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_ENDPOINT = "/monitoring"; | ||||||
|     public static final String EXAM_MONITORING_INSTRUCTION_ENDPOINT = "/instruction"; |     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_DISABLE_CONNECTION_ENDPOINT = "/disable-connection"; | ||||||
|     public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; |     public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; | ||||||
|     public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = |     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.JsonCreator; | ||||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | import com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
|  | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||||
| import com.fasterxml.jackson.annotation.JsonProperty; | import com.fasterxml.jackson.annotation.JsonProperty; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
| 
 | 
 | ||||||
|  | @JsonIgnoreProperties(ignoreUnknown = true) | ||||||
| public class ClientConnectionData { | public class ClientConnectionData { | ||||||
| 
 | 
 | ||||||
|     public static final String ATTR_CLIENT_CONNECTION = "clientConnection"; |     public static final String ATTR_CLIENT_CONNECTION = "clientConnection"; | ||||||
|     public static final String ATTR_INDICATOR_VALUE = "indicatorValues"; |     public static final String ATTR_INDICATOR_VALUE = "indicatorValues"; | ||||||
|     public static final String ATTR_MISSING_PING = "missingPing"; |     public static final String ATTR_MISSING_PING = "missingPing"; | ||||||
|  |     public static final String ATTR_PENDING_NOTIFICATION = "pendingNotification"; | ||||||
| 
 | 
 | ||||||
|     @JsonProperty(ATTR_CLIENT_CONNECTION) |     @JsonProperty(ATTR_CLIENT_CONNECTION) | ||||||
|     public final ClientConnection clientConnection; |     public final ClientConnection clientConnection; | ||||||
|  | @ -56,6 +59,11 @@ public class ClientConnectionData { | ||||||
|         return this.missingPing; |         return this.missingPing; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @JsonProperty(ATTR_PENDING_NOTIFICATION) | ||||||
|  |     public Boolean pendingNotification() { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @JsonIgnore |     @JsonIgnore | ||||||
|     public Long getConnectionId() { |     public Long getConnectionId() { | ||||||
|         return this.clientConnection.id; |         return this.clientConnection.id; | ||||||
|  |  | ||||||
|  | @ -182,7 +182,7 @@ public class MonitoringClientConnection implements TemplateComposer { | ||||||
| 
 | 
 | ||||||
|         final RestCall<ClientConnectionData>.RestCallBuilder getConnectionData = |         final RestCall<ClientConnectionData>.RestCallBuilder getConnectionData = | ||||||
|                 restService.getBuilder(GetClientConnectionData.class) |                 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); |                         .withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); | ||||||
| 
 | 
 | ||||||
|         final ClientConnectionData connectionData = getConnectionData |         final ClientConnectionData connectionData = getConnectionData | ||||||
|  |  | ||||||
|  | @ -181,7 +181,7 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
| 
 | 
 | ||||||
|         final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCall = |         final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCall = | ||||||
|                 restService.getBuilder(GetClientConnectionDataList.class) |                 restService.getBuilder(GetClientConnectionDataList.class) | ||||||
|                         .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()); |                         .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()); | ||||||
| 
 | 
 | ||||||
|         final ClientConnectionTable clientTable = new ClientConnectionTable( |         final ClientConnectionTable clientTable = new ClientConnectionTable( | ||||||
|                 this.pageService, |                 this.pageService, | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ public class DisableClientConnection extends RestCall<String> { | ||||||
|                 HttpMethod.POST, |                 HttpMethod.POST, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_MONITORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.PARENT_MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT); |                         + API.EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ public class GetClientConnectionData extends RestCall<ClientConnectionData> { | ||||||
|                 HttpMethod.GET, |                 HttpMethod.GET, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT + |                 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); |                         API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -37,7 +37,7 @@ public class GetClientConnectionDataList extends RestCall<Collection<ClientConne | ||||||
|                 HttpMethod.GET, |                 HttpMethod.GET, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 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, |                 HttpMethod.POST, | ||||||
|                 MediaType.APPLICATION_JSON_UTF8, |                 MediaType.APPLICATION_JSON_UTF8, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_MONITORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.PARENT_MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.EXAM_MONITORING_INSTRUCTION_ENDPOINT); |                         + API.EXAM_MONITORING_INSTRUCTION_ENDPOINT); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -93,7 +93,7 @@ public class InstructionProcessor { | ||||||
|                 null); |                 null); | ||||||
| 
 | 
 | ||||||
|         processInstruction(() -> this.restService.getBuilder(PropagateInstruction.class) |         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) |                 .withBody(clientInstruction) | ||||||
|                 .call() |                 .call() | ||||||
|                 .getOrThrow(), |                 .getOrThrow(), | ||||||
|  | @ -124,7 +124,7 @@ public class InstructionProcessor { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         processInstruction(() -> this.restService.getBuilder(DisableClientConnection.class) |         processInstruction(() -> this.restService.getBuilder(DisableClientConnection.class) | ||||||
|                 .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examId)) |                 .withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(examId)) | ||||||
|                 .withFormParam( |                 .withFormParam( | ||||||
|                         Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN, |                         Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN, | ||||||
|                         StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR)) |                         StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR)) | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| package ch.ethz.seb.sebserver.webservice.servicelayer.dao; | package ch.ethz.seb.sebserver.webservice.servicelayer.dao; | ||||||
| 
 | 
 | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.List; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; | import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; | ||||||
|  | @ -26,4 +27,8 @@ public interface ClientEventDAO extends EntityDAO<ClientEvent, ClientEvent> { | ||||||
|             FilterMap filterMap, |             FilterMap filterMap, | ||||||
|             Predicate<ExtendedClientEvent> predicate); |             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.math.BigDecimal; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.List; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.function.Predicate; | 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.model.session.ExtendedClientEvent; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | 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; | ||||||
| import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtensionMapper.ConnectionEventJoinRecord; | import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtensionMapper.ConnectionEventJoinRecord; | ||||||
| import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport; | import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport; | ||||||
|  | @ -183,6 +185,43 @@ public class ClientEventDAOImpl implements ClientEventDAO { | ||||||
|                 .collect(Collectors.toList())); |                 .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 |     @Override | ||||||
|     @Transactional |     @Transactional | ||||||
|     public Result<ClientEvent> createNew(final ClientEvent data) { |     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 |  * 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. */ |  * If there is an instruction in the queue for a specified SEB Client. */ | ||||||
| public interface SEBInstructionService { | public interface SEBClientInstructionService { | ||||||
| 
 | 
 | ||||||
|     /** Get the underling WebserviceInfo |     /** 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; | package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; | ||||||
| 
 | 
 | ||||||
| import org.apache.commons.lang3.StringUtils; |  | ||||||
| import org.joda.time.DateTime; | import org.joda.time.DateTime; | ||||||
| import org.joda.time.DateTimeZone; | 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.gbl.model.exam.Indicator; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; | ||||||
| 
 | 
 | ||||||
|  | @ -21,7 +19,6 @@ public abstract class AbstractClientIndicator implements ClientIndicator { | ||||||
|     protected Long examId; |     protected Long examId; | ||||||
|     protected Long connectionId; |     protected Long connectionId; | ||||||
|     protected boolean cachingEnabled; |     protected boolean cachingEnabled; | ||||||
|     protected String[] tags; |  | ||||||
| 
 | 
 | ||||||
|     protected double currentValue = Double.NaN; |     protected double currentValue = Double.NaN; | ||||||
| 
 | 
 | ||||||
|  | @ -34,15 +31,6 @@ public abstract class AbstractClientIndicator implements ClientIndicator { | ||||||
|         this.examId = (indicatorDefinition != null) ? indicatorDefinition.examId : null; |         this.examId = (indicatorDefinition != null) ? indicatorDefinition.examId : null; | ||||||
|         this.connectionId = connectionId; |         this.connectionId = connectionId; | ||||||
|         this.cachingEnabled = cachingEnabled; |         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 |     @Override | ||||||
|  | @ -55,6 +43,10 @@ public abstract class AbstractClientIndicator implements ClientIndicator { | ||||||
|         return this.connectionId; |         return this.connectionId; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void reset() { | ||||||
|  |         this.currentValue = Double.NaN; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public double getValue() { |     public double getValue() { | ||||||
|         if (Double.isNaN(this.currentValue) || !this.cachingEnabled) { |         if (Double.isNaN(this.currentValue) || !this.cachingEnabled) { | ||||||
|  |  | ||||||
|  | @ -17,6 +17,12 @@ import java.util.List; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.stream.Collectors; | 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; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; | import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | 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 Set<EventType> observed; | ||||||
|     private final List<Integer> eventTypeIds; |     private final List<Integer> eventTypeIds; | ||||||
|     private final ClientEventRecordMapper clientEventRecordMapper; |     private final ClientEventRecordMapper clientEventRecordMapper; | ||||||
|  |     protected String[] tags; | ||||||
| 
 | 
 | ||||||
|     protected AbstractLogLevelCountIndicator( |     protected AbstractLogLevelCountIndicator( | ||||||
|             final ClientEventRecordMapper clientEventRecordMapper, |             final ClientEventRecordMapper clientEventRecordMapper, | ||||||
|  | @ -38,6 +45,20 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractClientIndic | ||||||
|         this.eventTypeIds = Utils.immutableListOf(Arrays.stream(eventTypes) |         this.eventTypeIds = Utils.immutableListOf(Arrays.stream(eventTypes) | ||||||
|                 .map(et -> et.id) |                 .map(et -> et.id) | ||||||
|                 .collect(Collectors.toList())); |                 .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 |     @Override | ||||||
|  | @ -47,12 +68,40 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractClientIndic | ||||||
|                 .where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId)) |                 .where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId)) | ||||||
|                 .and(ClientEventRecordDynamicSqlSupport.type, isIn(this.eventTypeIds)) |                 .and(ClientEventRecordDynamicSqlSupport.type, isIn(this.eventTypeIds)) | ||||||
|                 .and(ClientEventRecordDynamicSqlSupport.serverTime, isLessThan(timestamp)) |                 .and(ClientEventRecordDynamicSqlSupport.serverTime, isLessThan(timestamp)) | ||||||
|  |                 .and( | ||||||
|  |                         ClientEventRecordDynamicSqlSupport.text, | ||||||
|  |                         isLikeWhenPresent(getfirstTagSQL()), | ||||||
|  |                         getSubTagSQL()) | ||||||
|                 .build() |                 .build() | ||||||
|                 .execute(); |                 .execute(); | ||||||
| 
 | 
 | ||||||
|         return errors.doubleValue(); |         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 |     @Override | ||||||
|     public void notifyValueChange(final ClientEvent event) { |     public void notifyValueChange(final ClientEvent event) { | ||||||
|         if (this.tags == null || this.tags.length == 0) { |         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.ClientConnectionData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; | 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.ClientIndicator; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication; | ||||||
| 
 | 
 | ||||||
| public class ClientConnectionDataInternal extends ClientConnectionData { | public class ClientConnectionDataInternal extends ClientConnectionData { | ||||||
| 
 | 
 | ||||||
|  | @ -31,12 +32,15 @@ public class ClientConnectionDataInternal extends ClientConnectionData { | ||||||
|     final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping; |     final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping; | ||||||
| 
 | 
 | ||||||
|     PingIntervalClientIndicator pingIndicator = null; |     PingIntervalClientIndicator pingIndicator = null; | ||||||
|  |     private final PendingNotificationIndication pendingNotificationIndication; | ||||||
| 
 | 
 | ||||||
|     protected ClientConnectionDataInternal( |     protected ClientConnectionDataInternal( | ||||||
|             final ClientConnection clientConnection, |             final ClientConnection clientConnection, | ||||||
|  |             final PendingNotificationIndication pendingNotificationIndication, | ||||||
|             final List<ClientIndicator> clientIndicators) { |             final List<ClientIndicator> clientIndicators) { | ||||||
| 
 | 
 | ||||||
|         super(clientConnection, clientIndicators); |         super(clientConnection, clientIndicators); | ||||||
|  |         this.pendingNotificationIndication = pendingNotificationIndication; | ||||||
| 
 | 
 | ||||||
|         this.indicatorMapping = new EnumMap<>(EventType.class); |         this.indicatorMapping = new EnumMap<>(EventType.class); | ||||||
|         for (final ClientIndicator clientIndicator : clientIndicators) { |         for (final ClientIndicator clientIndicator : clientIndicators) { | ||||||
|  | @ -62,17 +66,21 @@ public class ClientConnectionDataInternal extends ClientConnectionData { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Collection<ClientIndicator> getIndicatorMapping(final EventType eventType) { |     Collection<ClientIndicator> getIndicatorMapping(final EventType eventType) { | ||||||
|         if (!this.indicatorMapping.containsKey(eventType)) { |         return this.indicatorMapping.getOrDefault( | ||||||
|             return Collections.emptyList(); |                 eventType, | ||||||
|         } |                 Collections.emptyList()); | ||||||
| 
 |  | ||||||
|         return this.indicatorMapping.get(eventType); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     @JsonProperty("missingPing") |     @JsonProperty(ATTR_MISSING_PING) | ||||||
|     public Boolean getMissingPing() { |     public Boolean getMissingPing() { | ||||||
|         return this.pingIndicator.missingPing; |         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.exam.ExamAdminService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService; | 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.ExamSessionService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; | ||||||
| 
 | 
 | ||||||
| @Lazy | @Lazy | ||||||
| @Service | @Service | ||||||
|  | @ -45,14 +45,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService | ||||||
| 
 | 
 | ||||||
|     private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; |     private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; | ||||||
|     private final ClientConnectionDAO clientConnectionDAO; |     private final ClientConnectionDAO clientConnectionDAO; | ||||||
|     private final SEBInstructionService sebInstructionService; |     private final SEBClientInstructionService sebInstructionService; | ||||||
|     private final ExamAdminService examAdminService; |     private final ExamAdminService examAdminService; | ||||||
|     private final ExamSessionService examSessionService; |     private final ExamSessionService examSessionService; | ||||||
| 
 | 
 | ||||||
|     public ExamProctoringRoomServiceImpl( |     public ExamProctoringRoomServiceImpl( | ||||||
|             final RemoteProctoringRoomDAO remoteProctoringRoomDAO, |             final RemoteProctoringRoomDAO remoteProctoringRoomDAO, | ||||||
|             final ClientConnectionDAO clientConnectionDAO, |             final ClientConnectionDAO clientConnectionDAO, | ||||||
|             final SEBInstructionService sebInstructionService, |             final SEBClientInstructionService sebInstructionService, | ||||||
|             final ExamAdminService examAdminService, |             final ExamAdminService examAdminService, | ||||||
|             final ExamSessionService examSessionService) { |             final ExamSessionService examSessionService) { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ public class ExamSessionCacheService { | ||||||
| 
 | 
 | ||||||
|     private final ExamDAO examDAO; |     private final ExamDAO examDAO; | ||||||
|     private final ClientConnectionDAO clientConnectionDAO; |     private final ClientConnectionDAO clientConnectionDAO; | ||||||
|     private final ClientIndicatorFactory clientIndicatorFactory; |     private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; | ||||||
|     private final ExamConfigService sebExamConfigService; |     private final ExamConfigService sebExamConfigService; | ||||||
|     private final ClientEventRecordMapper clientEventRecordMapper; |     private final ClientEventRecordMapper clientEventRecordMapper; | ||||||
|     private final ExamUpdateHandler examUpdateHandler; |     private final ExamUpdateHandler examUpdateHandler; | ||||||
|  | @ -62,7 +62,7 @@ public class ExamSessionCacheService { | ||||||
|     protected ExamSessionCacheService( |     protected ExamSessionCacheService( | ||||||
|             final ExamDAO examDAO, |             final ExamDAO examDAO, | ||||||
|             final ClientConnectionDAO clientConnectionDAO, |             final ClientConnectionDAO clientConnectionDAO, | ||||||
|             final ClientIndicatorFactory clientIndicatorFactory, |             final InternalClientConnectionDataFactory internalClientConnectionDataFactory, | ||||||
|             final ExamConfigService sebExamConfigService, |             final ExamConfigService sebExamConfigService, | ||||||
|             final ClientEventRecordMapper clientEventRecordMapper, |             final ClientEventRecordMapper clientEventRecordMapper, | ||||||
|             final ExamUpdateHandler examUpdateHandler, |             final ExamUpdateHandler examUpdateHandler, | ||||||
|  | @ -70,7 +70,7 @@ public class ExamSessionCacheService { | ||||||
| 
 | 
 | ||||||
|         this.examDAO = examDAO; |         this.examDAO = examDAO; | ||||||
|         this.clientConnectionDAO = clientConnectionDAO; |         this.clientConnectionDAO = clientConnectionDAO; | ||||||
|         this.clientIndicatorFactory = clientIndicatorFactory; |         this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; | ||||||
|         this.sebExamConfigService = sebExamConfigService; |         this.sebExamConfigService = sebExamConfigService; | ||||||
|         this.clientEventRecordMapper = clientEventRecordMapper; |         this.clientEventRecordMapper = clientEventRecordMapper; | ||||||
|         this.examUpdateHandler = examUpdateHandler; |         this.examUpdateHandler = examUpdateHandler; | ||||||
|  | @ -146,9 +146,7 @@ public class ExamSessionCacheService { | ||||||
|         if (clientConnection == null) { |         if (clientConnection == null) { | ||||||
|             return null; |             return null; | ||||||
|         } else { |         } else { | ||||||
|             return new ClientConnectionDataInternal( |             return this.internalClientConnectionDataFactory.createClientConnectionData(clientConnection); | ||||||
|                     clientConnection, |  | ||||||
|                     this.clientIndicatorFactory.createFor(clientConnection)); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -239,7 +237,8 @@ public class ExamSessionCacheService { | ||||||
|                 .byConnectionToken(connectionToken); |                 .byConnectionToken(connectionToken); | ||||||
| 
 | 
 | ||||||
|         if (result.hasError()) { |         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 null; | ||||||
|         } |         } | ||||||
|         return result.get(); |         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.ClientConnection.ConnectionStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | 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; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | 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.ExamSessionService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.PingHandlingStrategy; | 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.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; | import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException; | ||||||
| 
 | 
 | ||||||
| @Lazy | @Lazy | ||||||
|  | @ -63,7 +65,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic | ||||||
|     private final ClientConnectionDAO clientConnectionDAO; |     private final ClientConnectionDAO clientConnectionDAO; | ||||||
|     private final PingHandlingStrategy pingHandlingStrategy; |     private final PingHandlingStrategy pingHandlingStrategy; | ||||||
|     private final SEBClientConfigDAO sebClientConfigDAO; |     private final SEBClientConfigDAO sebClientConfigDAO; | ||||||
|     private final SEBInstructionService sebInstructionService; |     private final SEBClientInstructionService sebInstructionService; | ||||||
|  |     private final SEBClientNotificationService sebClientNotificationService; | ||||||
|     private final WebserviceInfo webserviceInfo; |     private final WebserviceInfo webserviceInfo; | ||||||
|     private final ExamAdminService examAdminService; |     private final ExamAdminService examAdminService; | ||||||
| 
 | 
 | ||||||
|  | @ -72,7 +75,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic | ||||||
|             final EventHandlingStrategyFactory eventHandlingStrategyFactory, |             final EventHandlingStrategyFactory eventHandlingStrategyFactory, | ||||||
|             final PingHandlingStrategyFactory pingHandlingStrategyFactory, |             final PingHandlingStrategyFactory pingHandlingStrategyFactory, | ||||||
|             final SEBClientConfigDAO sebClientConfigDAO, |             final SEBClientConfigDAO sebClientConfigDAO, | ||||||
|             final SEBInstructionService sebInstructionService, |             final SEBClientInstructionService sebInstructionService, | ||||||
|  |             final SEBClientNotificationService sebClientNotificationService, | ||||||
|             final ExamAdminService examAdminService) { |             final ExamAdminService examAdminService) { | ||||||
| 
 | 
 | ||||||
|         this.examSessionService = examSessionService; |         this.examSessionService = examSessionService; | ||||||
|  | @ -83,6 +87,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic | ||||||
|         this.eventHandlingStrategy = eventHandlingStrategyFactory.get(); |         this.eventHandlingStrategy = eventHandlingStrategyFactory.get(); | ||||||
|         this.sebClientConfigDAO = sebClientConfigDAO; |         this.sebClientConfigDAO = sebClientConfigDAO; | ||||||
|         this.sebInstructionService = sebInstructionService; |         this.sebInstructionService = sebInstructionService; | ||||||
|  |         this.sebClientNotificationService = sebClientNotificationService; | ||||||
|         this.webserviceInfo = sebInstructionService.getWebserviceInfo(); |         this.webserviceInfo = sebInstructionService.getWebserviceInfo(); | ||||||
|         this.examAdminService = examAdminService; |         this.examAdminService = examAdminService; | ||||||
|     } |     } | ||||||
|  | @ -519,9 +524,14 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic | ||||||
|                     event, |                     event, | ||||||
|                     activeClientConnection.getConnectionId())); |                     activeClientConnection.getConnectionId())); | ||||||
| 
 | 
 | ||||||
|  |             if (event.eventType == EventType.NOTIFICATION || event.eventType == EventType.NOTIFICATION_CONFIRMED) { | ||||||
|  |                 // notify notification service | ||||||
|  |                 this.sebClientNotificationService.notifyNewNotification(activeClientConnection.getConnectionId()); | ||||||
|  |             } else { | ||||||
|                 // update indicators |                 // update indicators | ||||||
|                 activeClientConnection.getIndicatorMapping(event.eventType) |                 activeClientConnection.getIndicatorMapping(event.eventType) | ||||||
|                         .forEach(indicator -> indicator.notifyValueChange(event)); |                         .forEach(indicator -> indicator.notifyValueChange(event)); | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|             log.warn("No active ClientConnection found for connectionToken: {}", connectionToken); |             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.datalayer.batis.model.ClientInstructionRecord; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; | 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.dao.ClientInstructionDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; | ||||||
| 
 | 
 | ||||||
| @Lazy | @Lazy | ||||||
| @Service | @Service | ||||||
| @WebServiceProfile | @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 int INSTRUCTION_QUEUE_MAX_SIZE = 10; | ||||||
|     private static final String JSON_INST = "instruction"; |     private static final String JSON_INST = "instruction"; | ||||||
|  | @ -57,7 +57,7 @@ public class SEBInstructionServiceImpl implements SEBInstructionService { | ||||||
| 
 | 
 | ||||||
|     private long lastRefresh = 0; |     private long lastRefresh = 0; | ||||||
| 
 | 
 | ||||||
|     public SEBInstructionServiceImpl( |     public SEBClientInstructionServiceImpl( | ||||||
|             final WebserviceInfo webserviceInfo, |             final WebserviceInfo webserviceInfo, | ||||||
|             final ClientConnectionDAO clientConnectionDAO, |             final ClientConnectionDAO clientConnectionDAO, | ||||||
|             final ClientInstructionDAO clientInstructionDAO, |             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.exam.Exam; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | 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.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.session.ClientInstruction; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.user.UserRole; | import ch.ethz.seb.sebserver.gbl.model.user.UserRole; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | 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.dao.FilterMap; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; | 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.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 | @WebServiceProfile | ||||||
| @RestController | @RestController | ||||||
|  | @ -62,21 +64,24 @@ public class ExamMonitoringController { | ||||||
| 
 | 
 | ||||||
|     private final SEBClientConnectionService sebClientConnectionService; |     private final SEBClientConnectionService sebClientConnectionService; | ||||||
|     private final ExamSessionService examSessionService; |     private final ExamSessionService examSessionService; | ||||||
|     private final SEBInstructionService sebInstructionService; |     private final SEBClientInstructionService sebClientInstructionService; | ||||||
|     private final AuthorizationService authorization; |     private final AuthorizationService authorization; | ||||||
|     private final PaginationService paginationService; |     private final PaginationService paginationService; | ||||||
|  |     private final SEBClientNotificationService sebClientNotificationService; | ||||||
| 
 | 
 | ||||||
|     public ExamMonitoringController( |     public ExamMonitoringController( | ||||||
|             final SEBClientConnectionService sebClientConnectionService, |             final SEBClientConnectionService sebClientConnectionService, | ||||||
|             final SEBInstructionService sebInstructionService, |             final SEBClientInstructionService sebClientInstructionService, | ||||||
|             final AuthorizationService authorization, |             final AuthorizationService authorization, | ||||||
|             final PaginationService paginationService) { |             final PaginationService paginationService, | ||||||
|  |             final SEBClientNotificationService sebClientNotificationService) { | ||||||
| 
 | 
 | ||||||
|         this.sebClientConnectionService = sebClientConnectionService; |         this.sebClientConnectionService = sebClientConnectionService; | ||||||
|         this.examSessionService = sebClientConnectionService.getExamSessionService(); |         this.examSessionService = sebClientConnectionService.getExamSessionService(); | ||||||
|         this.sebInstructionService = sebInstructionService; |         this.sebClientInstructionService = sebClientInstructionService; | ||||||
|         this.authorization = authorization; |         this.authorization = authorization; | ||||||
|         this.paginationService = paginationService; |         this.paginationService = paginationService; | ||||||
|  |         this.sebClientNotificationService = sebClientNotificationService; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** This is called by Spring to initialize the WebDataBinder and is used here to |     /** This is called by Spring to initialize the WebDataBinder and is used here to | ||||||
|  | @ -150,7 +155,7 @@ public class ExamMonitoringController { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @RequestMapping( |     @RequestMapping( | ||||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT, |             path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT, | ||||||
|             method = RequestMethod.GET, |             method = RequestMethod.GET, | ||||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, |             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) |             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||||
|  | @ -159,7 +164,7 @@ public class ExamMonitoringController { | ||||||
|                     name = API.PARAM_INSTITUTION_ID, |                     name = API.PARAM_INSTITUTION_ID, | ||||||
|                     required = true, |                     required = true, | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     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) { |             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { | ||||||
| 
 | 
 | ||||||
|         // check overall privilege |         // check overall privilege | ||||||
|  | @ -194,7 +199,8 @@ public class ExamMonitoringController { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @RequestMapping( |     @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, |             method = RequestMethod.GET, | ||||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, |             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) |             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||||
|  | @ -203,7 +209,7 @@ public class ExamMonitoringController { | ||||||
|                     name = API.PARAM_INSTITUTION_ID, |                     name = API.PARAM_INSTITUTION_ID, | ||||||
|                     required = true, |                     required = true, | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     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) { |             @PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) { | ||||||
| 
 | 
 | ||||||
|         // check overall privilege |         // check overall privilege | ||||||
|  | @ -226,7 +232,8 @@ public class ExamMonitoringController { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @RequestMapping( |     @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, |             method = RequestMethod.POST, | ||||||
|             consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) |             consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||||
|     public void registerInstruction( |     public void registerInstruction( | ||||||
|  | @ -234,14 +241,64 @@ public class ExamMonitoringController { | ||||||
|                     name = API.PARAM_INSTITUTION_ID, |                     name = API.PARAM_INSTITUTION_ID, | ||||||
|                     required = true, |                     required = true, | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     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) { |             @Valid @RequestBody final ClientInstruction clientInstruction) { | ||||||
| 
 | 
 | ||||||
|         this.sebInstructionService.registerInstruction(clientInstruction); |         this.sebClientInstructionService.registerInstruction(clientInstruction); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @RequestMapping( |     @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, |             method = RequestMethod.POST, | ||||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) |             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | ||||||
|     public void disableConnection( |     public void disableConnection( | ||||||
|  | @ -249,7 +306,7 @@ public class ExamMonitoringController { | ||||||
|                     name = API.PARAM_INSTITUTION_ID, |                     name = API.PARAM_INSTITUTION_ID, | ||||||
|                     required = true, |                     required = true, | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     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( |             @RequestParam( | ||||||
|                     name = Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN, |                     name = Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN, | ||||||
|                     required = true) final String connectionToken) { |                     required = true) final String connectionToken) { | ||||||
|  | @ -266,7 +323,6 @@ public class ExamMonitoringController { | ||||||
|                     .disableConnection(connectionToken, institutionId) |                     .disableConnection(connectionToken, institutionId) | ||||||
|                     .getOrThrow(); |                     .getOrThrow(); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private boolean hasRunningExamPrivilege(final Long examId, final Long institution) { |     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.ExamProctoringRoomService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; | 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.ExamSessionService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; | ||||||
| 
 | 
 | ||||||
| @WebServiceProfile | @WebServiceProfile | ||||||
| @RestController | @RestController | ||||||
|  | @ -55,14 +55,14 @@ public class ExamProctoringController { | ||||||
| 
 | 
 | ||||||
|     private final ExamProctoringRoomService examProcotringRoomService; |     private final ExamProctoringRoomService examProcotringRoomService; | ||||||
|     private final ExamAdminService examAdminService; |     private final ExamAdminService examAdminService; | ||||||
|     private final SEBInstructionService sebInstructionService; |     private final SEBClientInstructionService sebInstructionService; | ||||||
|     private final AuthorizationService authorization; |     private final AuthorizationService authorization; | ||||||
|     private final ExamSessionService examSessionService; |     private final ExamSessionService examSessionService; | ||||||
| 
 | 
 | ||||||
|     public ExamProctoringController( |     public ExamProctoringController( | ||||||
|             final ExamProctoringRoomService examProcotringRoomService, |             final ExamProctoringRoomService examProcotringRoomService, | ||||||
|             final ExamAdminService examAdminService, |             final ExamAdminService examAdminService, | ||||||
|             final SEBInstructionService sebInstructionService, |             final SEBClientInstructionService sebInstructionService, | ||||||
|             final AuthorizationService authorization, |             final AuthorizationService authorization, | ||||||
|             final ExamSessionService examSessionService) { |             final ExamSessionService examSessionService) { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -918,8 +918,8 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
|         final Result<Indicator> newIndicatorResult = restService |         final Result<Indicator> newIndicatorResult = restService | ||||||
|                 .getBuilder(NewIndicator.class) |                 .getBuilder(NewIndicator.class) | ||||||
|                 .withFormParam(Domain.INDICATOR.ATTR_EXAM_ID, exam.getModelId()) |                 .withFormParam(Domain.INDICATOR.ATTR_EXAM_ID, exam.getModelId()) | ||||||
|                 .withFormParam(Domain.INDICATOR.ATTR_NAME, "Ping") |                 .withFormParam(Domain.INDICATOR.ATTR_NAME, "Errors") | ||||||
|                 .withFormParam(Domain.INDICATOR.ATTR_TYPE, IndicatorType.LAST_PING.name) |                 .withFormParam(Domain.INDICATOR.ATTR_TYPE, IndicatorType.ERROR_COUNT.name) | ||||||
|                 .withFormParam(Domain.INDICATOR.ATTR_COLOR, "000001") |                 .withFormParam(Domain.INDICATOR.ATTR_COLOR, "000001") | ||||||
|                 .call(); |                 .call(); | ||||||
| 
 | 
 | ||||||
|  | @ -927,7 +927,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
|         assertFalse(newIndicatorResult.hasError()); |         assertFalse(newIndicatorResult.hasError()); | ||||||
|         final Indicator newIndicator = newIndicatorResult.get(); |         final Indicator newIndicator = newIndicatorResult.get(); | ||||||
| 
 | 
 | ||||||
|         assertEquals("Ping", newIndicator.name); |         assertEquals("Errors", newIndicator.name); | ||||||
|         assertEquals("000001", newIndicator.defaultColor); |         assertEquals("000001", newIndicator.defaultColor); | ||||||
| 
 | 
 | ||||||
|         final Indicator indicatorToSave = new Indicator( |         final Indicator indicatorToSave = new Indicator( | ||||||
|  | @ -951,7 +951,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
|         assertFalse(savedIndicatorResult.hasError()); |         assertFalse(savedIndicatorResult.hasError()); | ||||||
|         final Indicator savedIndicator = savedIndicatorResult.get(); |         final Indicator savedIndicator = savedIndicatorResult.get(); | ||||||
| 
 | 
 | ||||||
|         assertEquals("Ping", savedIndicator.name); |         assertEquals("Errors", savedIndicator.name); | ||||||
|         assertEquals("000001", savedIndicator.defaultColor); |         assertEquals("000001", savedIndicator.defaultColor); | ||||||
|         final Collection<Threshold> thresholds = savedIndicator.getThresholds(); |         final Collection<Threshold> thresholds = savedIndicator.getThresholds(); | ||||||
|         assertFalse(thresholds.isEmpty()); |         assertFalse(thresholds.isEmpty()); | ||||||
|  | @ -2089,7 +2089,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
|         // get SEB connections |         // get SEB connections | ||||||
|         Result<Collection<ClientConnectionData>> connectionsCall = |         Result<Collection<ClientConnectionData>> connectionsCall = | ||||||
|                 restService.getBuilder(GetClientConnectionDataList.class) |                 restService.getBuilder(GetClientConnectionDataList.class) | ||||||
|                         .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) |                         .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()) | ||||||
|                         .call(); |                         .call(); | ||||||
| 
 | 
 | ||||||
|         assertNotNull(connectionsCall); |         assertNotNull(connectionsCall); | ||||||
|  | @ -2122,7 +2122,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
|             // send quit instruction |             // send quit instruction | ||||||
|             connectionsCall = |             connectionsCall = | ||||||
|                     restService.getBuilder(GetClientConnectionDataList.class) |                     restService.getBuilder(GetClientConnectionDataList.class) | ||||||
|                             .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) |                             .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()) | ||||||
|                             .call(); |                             .call(); | ||||||
| 
 | 
 | ||||||
|             assertNotNull(connectionsCall); |             assertNotNull(connectionsCall); | ||||||
|  | @ -2141,7 +2141,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
|                     null); |                     null); | ||||||
| 
 | 
 | ||||||
|             final Result<String> instructionCall = restService.getBuilder(PropagateInstruction.class) |             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) |                     .withBody(clientInstruction) | ||||||
|                     .call(); |                     .call(); | ||||||
| 
 | 
 | ||||||
|  | @ -2155,7 +2155,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
| 
 | 
 | ||||||
|         connectionsCall = |         connectionsCall = | ||||||
|                 restService.getBuilder(GetClientConnectionDataList.class) |                 restService.getBuilder(GetClientConnectionDataList.class) | ||||||
|                         .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) |                         .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()) | ||||||
|                         .call(); |                         .call(); | ||||||
| 
 | 
 | ||||||
|         assertNotNull(connectionsCall); |         assertNotNull(connectionsCall); | ||||||
|  | @ -2171,7 +2171,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
| 
 | 
 | ||||||
|         // disable connection |         // disable connection | ||||||
|         final Result<String> disableCall = restService.getBuilder(DisableClientConnection.class) |         final Result<String> disableCall = restService.getBuilder(DisableClientConnection.class) | ||||||
|                 .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) |                 .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()) | ||||||
|                 .withFormParam( |                 .withFormParam( | ||||||
|                         Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN, |                         Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN, | ||||||
|                         conData.clientConnection.connectionToken) |                         conData.clientConnection.connectionToken) | ||||||
|  | @ -2180,7 +2180,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
|         assertFalse(disableCall.hasError()); |         assertFalse(disableCall.hasError()); | ||||||
|         connectionsCall = |         connectionsCall = | ||||||
|                 restService.getBuilder(GetClientConnectionDataList.class) |                 restService.getBuilder(GetClientConnectionDataList.class) | ||||||
|                         .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) |                         .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()) | ||||||
|                         .call(); |                         .call(); | ||||||
| 
 | 
 | ||||||
|         assertNotNull(connectionsCall); |         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 | 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 | INSERT IGNORE INTO threshold VALUES | ||||||
|     (1, 1, 1000.0000, '22b14c', null), |     (1, 1, 1000.0000, '22b14c', null), | ||||||
|     (2, 1, 2000.0000, 'ff7e00', 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 | INSERT IGNORE INTO view VALUES | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti