Merge remote-tracking branch 'origin/dev-1.1-patch-1' into development
Conflicts: src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java
This commit is contained in:
commit
31fbfa373c
6 changed files with 82 additions and 37 deletions
|
@ -242,7 +242,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_CONFIRM_NOTIFICATION)
|
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_CONFIRM_NOTIFICATION)
|
||||||
.withParentEntityKey(parentEntityKey)
|
.withParentEntityKey(parentEntityKey)
|
||||||
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
||||||
.withExec(action -> this.confirmNotification(action, connectionData))
|
.withExec(action -> this.confirmNotification(action, connectionData, t))
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.create())
|
.create())
|
||||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||||
|
@ -256,8 +256,10 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
||||||
.withSelect(
|
.withSelect(
|
||||||
() -> notificationTable.getSelection(),
|
() -> notificationTable.getSelection(),
|
||||||
action -> this.confirmNotification(action, connectionData),
|
action -> this.confirmNotification(action, connectionData, notificationTable),
|
||||||
|
|
||||||
NOTIFICATION_LIST_NO_SELECTION_KEY)
|
NOTIFICATION_LIST_NO_SELECTION_KEY)
|
||||||
|
.noEventPropagation()
|
||||||
|
|
||||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||||
|
|
||||||
|
@ -409,9 +411,10 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
|
|
||||||
private PageAction confirmNotification(
|
private PageAction confirmNotification(
|
||||||
final PageAction pageAction,
|
final PageAction pageAction,
|
||||||
final ClientConnectionData connectionData) {
|
final ClientConnectionData connectionData,
|
||||||
|
final EntityTable<ClientNotification> table) {
|
||||||
|
|
||||||
final EntityKey entityKey = pageAction.getSingleSelection();
|
final EntityKey entityKey = table.getSingleSelection();
|
||||||
final EntityKey parentEntityKey = pageAction.getParentEntityKey();
|
final EntityKey parentEntityKey = pageAction.getParentEntityKey();
|
||||||
|
|
||||||
this.pageService.getRestService()
|
this.pageService.getRestService()
|
||||||
|
@ -422,13 +425,8 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
return pageAction
|
table.reset();
|
||||||
.withEntityKey(
|
return pageAction;
|
||||||
new EntityKey(connectionData.getConnectionId(),
|
|
||||||
EntityType.CLIENT_CONNECTION))
|
|
||||||
.withAttribute(
|
|
||||||
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
|
||||||
connectionData.clientConnection.connectionToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getClientTime(final ClientEvent event) {
|
private String getClientTime(final ClientEvent event) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
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;
|
||||||
|
@ -28,10 +29,29 @@ public interface ClientEventDAO extends EntityDAO<ClientEvent, ClientEvent> {
|
||||||
FilterMap filterMap,
|
FilterMap filterMap,
|
||||||
Predicate<ExtendedClientEvent> predicate);
|
Predicate<ExtendedClientEvent> predicate);
|
||||||
|
|
||||||
|
/** Get a specified notification by id (PK)
|
||||||
|
*
|
||||||
|
* @param notificationId The PK of the notification
|
||||||
|
* @return Result refer to the specified ClientNotification or to an error when happened */
|
||||||
Result<ClientNotification> getPendingNotification(Long notificationId);
|
Result<ClientNotification> getPendingNotification(Long notificationId);
|
||||||
|
|
||||||
|
/** Get all pending notifications for a given client connection.
|
||||||
|
*
|
||||||
|
* @param clientConnectionId The client connection identifier
|
||||||
|
* @return Result refer to the list of pending notifications or to an error when happened */
|
||||||
Result<List<ClientNotification>> getPendingNotifications(Long clientConnectionId);
|
Result<List<ClientNotification>> getPendingNotifications(Long clientConnectionId);
|
||||||
|
|
||||||
|
/** Get all identifiers (PKs) of client connections of a given exam that has any pending notification
|
||||||
|
*
|
||||||
|
* @param examId the exam identifier
|
||||||
|
* @return Result refer to a set of identifiers of client connections or to an error when happened */
|
||||||
|
Result<Set<Long>> getClientConnectionIdsWithPendingNotification(Long examId);
|
||||||
|
|
||||||
|
/** Used to confirm a pending notification so that the notification is not pending anymore
|
||||||
|
*
|
||||||
|
* @param notificationId the notification identifier
|
||||||
|
* @param clientConnectionId the client connection identifier
|
||||||
|
* @return Result refer to the confirmed notification or to en error when happened */
|
||||||
Result<ClientNotification> confirmPendingNotification(Long notificationId, Long clientConnectionId);
|
Result<ClientNotification> confirmPendingNotification(Long notificationId, Long clientConnectionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
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.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.model.session.ClientNotification;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
|
@ -212,6 +213,31 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Set<Long>> getClientConnectionIdsWithPendingNotification(final Long examId) {
|
||||||
|
return Result.tryCatch(() -> this.clientEventRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.leftJoin(ClientConnectionRecordDynamicSqlSupport.clientConnectionRecord)
|
||||||
|
.on(
|
||||||
|
ClientConnectionRecordDynamicSqlSupport.id,
|
||||||
|
equalTo(ClientEventRecordDynamicSqlSupport.clientConnectionId))
|
||||||
|
.where(
|
||||||
|
ClientConnectionRecordDynamicSqlSupport.examId,
|
||||||
|
isEqualToWhenPresent(examId))
|
||||||
|
.and(
|
||||||
|
ClientConnectionRecordDynamicSqlSupport.status,
|
||||||
|
isEqualTo(ConnectionStatus.ACTIVE.name()))
|
||||||
|
.and(
|
||||||
|
ClientEventRecordDynamicSqlSupport.type,
|
||||||
|
isEqualTo(EventType.NOTIFICATION.id))
|
||||||
|
.build()
|
||||||
|
.execute()
|
||||||
|
.stream()
|
||||||
|
.map(ClientEventRecord::getClientConnectionId)
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<ClientNotification> confirmPendingNotification(final Long notificationId,
|
public Result<ClientNotification> confirmPendingNotification(final Long notificationId,
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
@ -24,7 +25,7 @@ public interface SEBClientNotificationService {
|
||||||
*
|
*
|
||||||
* @param clientConnectionId the client connection identifier
|
* @param clientConnectionId the client connection identifier
|
||||||
* @return true if there is any pending notification for the specified client connection */
|
* @return true if there is any pending notification for the specified client connection */
|
||||||
Boolean hasAnyPendingNotification(Long clientConnectionId);
|
Boolean hasAnyPendingNotification(final ClientConnection clientConnection);
|
||||||
|
|
||||||
Result<List<ClientNotification>> getPendingNotifications(Long clientConnectionId);
|
Result<List<ClientNotification>> getPendingNotifications(Long clientConnectionId);
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class InternalClientConnectionDataFactory {
|
||||||
return new ClientConnectionDataInternal(
|
return new ClientConnectionDataInternal(
|
||||||
clientConnection,
|
clientConnection,
|
||||||
() -> this.sebClientNotificationService
|
() -> this.sebClientNotificationService
|
||||||
.hasAnyPendingNotification(clientConnection.id),
|
.hasAnyPendingNotification(clientConnection),
|
||||||
this.clientIndicatorFactory.createFor(clientConnection));
|
this.clientIndicatorFactory.createFor(clientConnection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.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.session.ClientInstruction.InstructionType;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
|
||||||
|
@ -43,6 +45,9 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
|
||||||
private final ClientEventDAO clientEventDAO;
|
private final ClientEventDAO clientEventDAO;
|
||||||
private final SEBClientInstructionService sebClientInstructionService;
|
private final SEBClientInstructionService sebClientInstructionService;
|
||||||
private final Set<Long> pendingNotifications = new HashSet<>();
|
private final Set<Long> pendingNotifications = new HashSet<>();
|
||||||
|
private final Set<Long> examUpdate = new HashSet<>();
|
||||||
|
|
||||||
|
private long lastUpdate = 0;
|
||||||
|
|
||||||
public SEBClientNotificationServiceImpl(
|
public SEBClientNotificationServiceImpl(
|
||||||
final ClientEventDAO clientEventDAO,
|
final ClientEventDAO clientEventDAO,
|
||||||
|
@ -52,31 +57,10 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
|
||||||
this.sebClientInstructionService = sebClientInstructionService;
|
this.sebClientInstructionService = sebClientInstructionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO find a better (faster) way to update pending notifications
|
|
||||||
// get them all at certain interval with background update for example
|
|
||||||
// or update them all on every monitoring call
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean hasAnyPendingNotification(final Long clientConnectionId) {
|
public Boolean hasAnyPendingNotification(final ClientConnection clientConnection) {
|
||||||
|
updateCache(clientConnection.examId);
|
||||||
if (this.pendingNotifications.contains(clientConnectionId)) {
|
return this.pendingNotifications.contains(clientConnection.id);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean hasAnyPendingNotification = !getPendingNotifications(clientConnectionId)
|
|
||||||
.getOr(Collections.emptyList())
|
|
||||||
.isEmpty();
|
|
||||||
|
|
||||||
if (hasAnyPendingNotification) {
|
|
||||||
// NOTE this is a quick and dirty way to keep cache pendingNotifications cache size short.
|
|
||||||
// TODO find a better way to do this.
|
|
||||||
if (this.pendingNotifications.size() > 100) {
|
|
||||||
this.pendingNotifications.clear();
|
|
||||||
}
|
|
||||||
this.pendingNotifications.add(clientConnectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasAnyPendingNotification;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -146,4 +130,20 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final void updateCache(final Long examId) {
|
||||||
|
if (System.currentTimeMillis() - this.lastUpdate > 5 * Constants.SECOND_IN_MILLIS) {
|
||||||
|
this.examUpdate.clear();
|
||||||
|
this.pendingNotifications.clear();
|
||||||
|
this.lastUpdate = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.examUpdate.contains(examId)) {
|
||||||
|
this.pendingNotifications.addAll(
|
||||||
|
this.clientEventDAO
|
||||||
|
.getClientConnectionIdsWithPendingNotification(examId)
|
||||||
|
.getOr(Collections.emptySet()));
|
||||||
|
this.examUpdate.add(examId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue