SEBSERV-182

This commit is contained in:
anhefti 2021-07-07 13:24:38 +02:00
parent c217d4d854
commit c7952b32bc
7 changed files with 101 additions and 50 deletions

View file

@ -36,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
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.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.content.MonitoringRunningExam.ProctoringUpdateErrorHandler;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.ResourceService; import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
@ -269,11 +270,14 @@ public class MonitoringClientConnection implements TemplateComposer {
final Supplier<EntityTable<ClientNotification>> notificationTableSupplier = _notificationTableSupplier; final Supplier<EntityTable<ClientNotification>> notificationTableSupplier = _notificationTableSupplier;
// server push update // server push update
final ProctoringUpdateErrorHandler proctoringUpdateErrorHandler =
new ProctoringUpdateErrorHandler(this.pageService, pageContext);
this.serverPushService.runServerPush( this.serverPushService.runServerPush(
new ServerPushContext( new ServerPushContext(
content, content,
Utils.truePredicate(), Utils.truePredicate(),
MonitoringRunningExam.createServerPushUpdateErrorHandler(this.pageService, pageContext)), proctoringUpdateErrorHandler),
this.pollInterval, this.pollInterval,
context -> clientConnectionDetails.updateData(), context -> clientConnectionDetails.updateData(),
context -> clientConnectionDetails.updateGUI(notificationTableSupplier, pageContext)); context -> clientConnectionDetails.updateGUI(notificationTableSupplier, pageContext));

View file

@ -149,13 +149,22 @@ public class MonitoringRunningExam implements TemplateComposer {
restService.getBuilder(GetClientConnectionDataList.class) restService.getBuilder(GetClientConnectionDataList.class)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()); .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId());
final ProctoringUpdateErrorHandler proctoringUpdateErrorHandler =
new ProctoringUpdateErrorHandler(this.pageService, pageContext);
final ServerPushContext pushContext = new ServerPushContext(
content,
Utils.truePredicate(),
proctoringUpdateErrorHandler);
final ClientConnectionTable clientTable = new ClientConnectionTable( final ClientConnectionTable clientTable = new ClientConnectionTable(
this.pageService, this.pageService,
tablePane, tablePane,
this.asyncRunner, this.asyncRunner,
exam, exam,
indicators, indicators,
restCall); restCall,
pushContext);
clientTable clientTable
.withDefaultAction( .withDefaultAction(
@ -172,10 +181,7 @@ public class MonitoringRunningExam implements TemplateComposer {
ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM)); ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM));
this.serverPushService.runServerPush( this.serverPushService.runServerPush(
new ServerPushContext( pushContext,
content,
context -> clientTable.getUpdateErrors() < 5,
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
this.pollInterval, this.pollInterval,
context -> clientTable.updateValues(), context -> clientTable.updateValues(),
updateTableGUI(clientTable)); updateTableGUI(clientTable));
@ -281,18 +287,26 @@ public class MonitoringRunningExam implements TemplateComposer {
} }
} }
final ProctoringUpdateErrorHandler proctoringUpdateErrorHandler =
new ProctoringUpdateErrorHandler(this.pageService, pageContext);
final ServerPushContext pushContext = new ServerPushContext(
parent,
Utils.truePredicate(),
proctoringUpdateErrorHandler);
this.monitoringProctoringService.initCollectingRoomActions( this.monitoringProctoringService.initCollectingRoomActions(
pushContext,
pageContext, pageContext,
actionBuilder, actionBuilder,
proctoringSettings, proctoringSettings,
proctoringGUIService); proctoringGUIService);
this.serverPushService.runServerPush( this.serverPushService.runServerPush(
new ServerPushContext( pushContext,
parent,
Utils.truePredicate(),
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
this.proctoringRoomUpdateInterval, this.proctoringRoomUpdateInterval,
context -> this.monitoringProctoringService.updateCollectingRoomActions( context -> this.monitoringProctoringService.updateCollectingRoomActions(
context,
pageContext, pageContext,
actionBuilder, actionBuilder,
proctoringSettings, proctoringSettings,
@ -480,30 +494,53 @@ public class MonitoringRunningExam implements TemplateComposer {
}; };
} }
static final Function<Exception, Boolean> createServerPushUpdateErrorHandler( static final class ProctoringUpdateErrorHandler implements Function<Exception, Boolean> {
final PageService pageService,
final PageContext pageContext) {
return error -> { private final PageService pageService;
log.error("Fialed to update server push: {}", error.getMessage()); private final PageContext pageContext;
private int errors = 0;
public ProctoringUpdateErrorHandler(
final PageService pageService,
final PageContext pageContext) {
this.pageService = pageService;
this.pageContext = pageContext;
}
private boolean checkUserSession() {
try { try {
pageService.getCurrentUser().get(); this.pageService.getCurrentUser().get();
return true;
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to verify current user after server push error: {}", e.getMessage()); try {
log.info("Force logout and session cleanup..."); this.pageContext.forwardToLoginPage();
pageContext.forwardToLoginPage(); final MessageBox logoutSuccess = new Message(
final MessageBox logoutSuccess = new Message( this.pageContext.getShell(),
pageContext.getShell(), this.pageService.getI18nSupport().getText("sebserver.logout"),
pageService.getI18nSupport().getText("sebserver.logout"), Utils.formatLineBreaks(
Utils.formatLineBreaks( this.pageService.getI18nSupport()
pageService.getI18nSupport().getText("sebserver.logout.invalid-session.message")), .getText("sebserver.logout.invalid-session.message")),
SWT.ICON_INFORMATION, SWT.ICON_INFORMATION,
pageService.getI18nSupport()); this.pageService.getI18nSupport());
logoutSuccess.open(null); logoutSuccess.open(null);
} catch (final Exception ee) {
log.warn("Unable to auto-logout: ", ee.getMessage());
}
return true; return true;
} }
return false; }
};
@Override
public Boolean apply(final Exception error) {
this.errors++;
log.error("Failed to update server push: {}", error.getMessage());
if (this.errors > 5) {
checkUserSession();
}
return this.errors > 5;
}
} }
} }

View file

@ -21,7 +21,7 @@ public final class ServerPushContext {
public final Composite anchor; public final Composite anchor;
public final Predicate<ServerPushContext> runAgain; public final Predicate<ServerPushContext> runAgain;
public final Function<Exception, Boolean> errorHandler; final Function<Exception, Boolean> errorHandler;
boolean internalStop = false; boolean internalStop = false;
public ServerPushContext( public ServerPushContext(
@ -36,6 +36,12 @@ public final class ServerPushContext {
this.runAgain = runAgain; this.runAgain = runAgain;
} }
public void reportError(final Exception error) {
if (this.errorHandler != null) {
this.internalStop = this.errorHandler.apply(error);
}
}
public boolean runAgain() { public boolean runAgain() {
return !this.internalStop && this.runAgain.test(this); return !this.internalStop && this.runAgain.test(this);
} }

View file

@ -61,6 +61,7 @@ import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor; import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
@ -95,6 +96,8 @@ public final class ClientConnectionTable {
private final AsyncRunner asyncRunner; private final AsyncRunner asyncRunner;
private final Exam exam; private final Exam exam;
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder; private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
private final ServerPushContext pushConext;
private final Map<Long, IndicatorData> indicatorMapping; private final Map<Long, IndicatorData> indicatorMapping;
private final Table table; private final Table table;
private final ColorData colorData; private final ColorData colorData;
@ -116,7 +119,7 @@ public final class ClientConnectionTable {
private boolean forceUpdateAll = false; private boolean forceUpdateAll = false;
private boolean updateInProgress = false; private boolean updateInProgress = false;
private int updateErrors = 0; //private int updateErrors = 0;
public ClientConnectionTable( public ClientConnectionTable(
final PageService pageService, final PageService pageService,
@ -124,12 +127,14 @@ public final class ClientConnectionTable {
final AsyncRunner asyncRunner, final AsyncRunner asyncRunner,
final Exam exam, final Exam exam,
final Collection<Indicator> indicators, final Collection<Indicator> indicators,
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder) { final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder,
final ServerPushContext pushConext) {
this.pageService = pageService; this.pageService = pageService;
this.asyncRunner = asyncRunner; this.asyncRunner = asyncRunner;
this.exam = exam; this.exam = exam;
this.restCallBuilder = restCallBuilder; this.restCallBuilder = restCallBuilder;
this.pushConext = pushConext;
final WidgetFactory widgetFactory = pageService.getWidgetFactory(); final WidgetFactory widgetFactory = pageService.getWidgetFactory();
final ResourceService resourceService = pageService.getResourceService(); final ResourceService resourceService = pageService.getResourceService();
@ -190,9 +195,9 @@ public final class ClientConnectionTable {
this.table.layout(); this.table.layout();
} }
public int getUpdateErrors() { // public int getUpdateErrors() {
return this.updateErrors; // return this.updateErrors;
} // }
public WidgetFactory getWidgetFactory() { public WidgetFactory getWidgetFactory() {
return this.pageService.getWidgetFactory(); return this.pageService.getWidgetFactory();
@ -322,14 +327,17 @@ public final class ClientConnectionTable {
} }
this.updateInProgress = true; this.updateInProgress = true;
this.asyncRunner.runAsync(this::updateValuesAsync); final boolean needsSync = this.tableMapping != null &&
this.table != null &&
this.tableMapping.size() != this.table.getItemCount();
this.asyncRunner.runAsync(() -> updateValuesAsync(needsSync));
} }
private void updateValuesAsync() { private void updateValuesAsync(final boolean needsSync) {
try { try {
if (this.statusFilterChanged || this.forceUpdateAll) { if (this.statusFilterChanged || this.forceUpdateAll || needsSync) {
this.toDelete.clear(); this.toDelete.clear();
this.toDelete.addAll(this.tableMapping.keySet()); this.toDelete.addAll(this.tableMapping.keySet());
} }
@ -337,8 +345,8 @@ public final class ClientConnectionTable {
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam) .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
.call() .call()
.get(error -> { .get(error -> {
log.error("Unexpected error while trying to get client connection table data: ", error);
recoverFromDisposedRestTemplate(error); recoverFromDisposedRestTemplate(error);
this.pushConext.reportError(error);
return Collections.emptyList(); return Collections.emptyList();
}) })
.forEach(data -> { .forEach(data -> {
@ -367,11 +375,9 @@ public final class ClientConnectionTable {
this.forceUpdateAll = false; this.forceUpdateAll = false;
this.updateInProgress = false; this.updateInProgress = false;
this.updateErrors = 0;
} catch (final Exception e) { } catch (final Exception e) {
log.error("Unexpected error while updating client connection table: ", e); this.pushConext.reportError(e);
this.updateErrors++;
} }
} }

View file

@ -47,6 +47,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder; import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
@ -152,6 +153,7 @@ public class MonitoringProctoringService {
} }
public void initCollectingRoomActions( public void initCollectingRoomActions(
final ServerPushContext pushContext,
final PageContext pageContext, final PageContext pageContext,
final PageActionBuilder actionBuilder, final PageActionBuilder actionBuilder,
final ProctoringServiceSettings proctoringSettings, final ProctoringServiceSettings proctoringSettings,
@ -159,6 +161,7 @@ public class MonitoringProctoringService {
proctoringGUIService.clearCollectingRoomActionState(); proctoringGUIService.clearCollectingRoomActionState();
updateCollectingRoomActions( updateCollectingRoomActions(
pushContext,
pageContext, pageContext,
actionBuilder, actionBuilder,
proctoringSettings, proctoringSettings,
@ -166,6 +169,7 @@ public class MonitoringProctoringService {
} }
public void updateCollectingRoomActions( public void updateCollectingRoomActions(
final ServerPushContext pushContext,
final PageContext pageContext, final PageContext pageContext,
final PageActionBuilder actionBuilder, final PageActionBuilder actionBuilder,
final ProctoringServiceSettings proctoringSettings, final ProctoringServiceSettings proctoringSettings,
@ -179,7 +183,7 @@ public class MonitoringProctoringService {
.getBuilder(GetCollectingRooms.class) .getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call() .call()
.onError(error -> log.error("Failed to update proctoring rooms on GUI {}", error.getMessage())) .onError(error -> pushContext.reportError(error))
.getOr(Collections.emptyList()) .getOr(Collections.emptyList())
.stream() .stream()
.forEach(room -> { .forEach(room -> {

View file

@ -99,9 +99,6 @@
}) })
window.addEventListener('unload', () => { window.addEventListener('unload', () => {
ZoomMtg.muteAll({
muteAll: true
});
ZoomMtg.endMeeting({}); ZoomMtg.endMeeting({});
}); });
</script> </script>

View file

@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService
public class ZoomWindowScriptResolverTest { public class ZoomWindowScriptResolverTest {
@Test @Test
public void testJitsiWindowScriptResolver() { public void testZoomWindowScriptResolver() {
final DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(); final DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
final Resource resource = defaultResourceLoader.getResource(ZoomWindowScriptResolver.RES_PATH); final Resource resource = defaultResourceLoader.getResource(ZoomWindowScriptResolver.RES_PATH);
final ZoomWindowScriptResolver zoomWindowScriptResolver = new ZoomWindowScriptResolver(resource); final ZoomWindowScriptResolver zoomWindowScriptResolver = new ZoomWindowScriptResolver(resource);
@ -166,9 +166,6 @@ public class ZoomWindowScriptResolverTest {
+ " })\r\n" + " })\r\n"
+ " \r\n" + " \r\n"
+ " window.addEventListener('unload', () => {\r\n" + " window.addEventListener('unload', () => {\r\n"
+ " ZoomMtg.muteAll({\r\n"
+ " muteAll: true\r\n"
+ " });\r\n"
+ " ZoomMtg.endMeeting({});\r\n" + " ZoomMtg.endMeeting({});\r\n"
+ " });\r\n" + " });\r\n"
+ " </script>\r\n" + " </script>\r\n"