diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java index ff41ba42..e2f1fd39 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java @@ -382,7 +382,7 @@ public final class Result { @Override public String toString() { - throw new RuntimeException("!!!!!!!!!!!!!"); + throw new RuntimeException("Result.toString is probably called by mistake !!!"); //return "Result [value=" + this.value + ", error=" + this.error + "]"; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java index 650531ab..1f324464 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java @@ -371,15 +371,18 @@ public class MonitoringClientConnection implements TemplateComposer { .call() .onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) .getOr(null); - final ProctoringGUIService proctoringGUIService = currentUser.getProctoringGUIService(); if (procotringSettings != null && procotringSettings.enableProctoring) { + final ProctoringGUIService proctoringGUIService = this.resourceService + .getCurrentUser() + .getProctoringGUIService(); actionBuilder .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) .withEntityKey(parentEntityKey) .withExec(action -> this.monitoringProctoringService.openOneToOneRoom( action, - connectionData, proctoringGUIService)) + connectionData, + proctoringGUIService)) .noEventPropagation() .publish() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java index f33c5ccd..9b544cf6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java @@ -36,6 +36,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEventListener; import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEventListener; import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; @@ -118,19 +119,7 @@ public class ActionPane implements TemplateComposer { continue; } - final PageAction action = (PageAction) actionItem.getData(ACTION_EVENT_CALL_KEY); - final Image image = event.activation - ? ad.icon.getImage(parent.getDisplay()) - : ad.icon.getGreyedImage(parent.getDisplay()); - actionItem.setImage(image); - if (event.activation) { - actionItem.setForeground(null); - } else { - actionItem.setForeground(new Color(parent.getDisplay(), new RGBA(150, 150, 150, 50))); - ActionPane.this.pageService.getPolyglotPageService().injectI18n( - actionItem, - (action != null) ? action.getTitle() : ad.title); - } + de_activate_action_icon(event, parent, actionItem); } if (event.decoration != null) { @@ -141,10 +130,33 @@ public class ActionPane implements TemplateComposer { ActionPane.this.pageService.getPolyglotPageService().injectI18n( actionItemToDecorate, event.decoration._2.title); } + + de_activate_action_icon(event, parent, actionItemToDecorate); } }); } + private void de_activate_action_icon( + final ActionActivationEvent event, + final Composite parent, + final TreeItem actionItemToDecorate) { + + final PageAction action = (PageAction) actionItemToDecorate.getData(ACTION_EVENT_CALL_KEY); + final Image image = event.activation + ? event.decoration._1.icon.getImage(parent.getDisplay()) + : event.decoration._1.icon.getGreyedImage(parent.getDisplay()); + actionItemToDecorate.setImage(image); + if (event.activation) { + actionItemToDecorate.setForeground(null); + } else { + actionItemToDecorate + .setForeground(new Color(parent.getDisplay(), new RGBA(150, 150, 150, 50))); + ActionPane.this.pageService.getPolyglotPageService().injectI18n( + actionItemToDecorate, + (action != null) ? action.getTitle() : event.decoration._1.title); + } + } + private TreeItem findAction( final Map actionTrees, final Composite parent, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java index e41ca1d7..16170cbd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java @@ -44,6 +44,7 @@ public class PageContextImpl implements PageContext { private static final Logger log = LoggerFactory.getLogger(PageContextImpl.class); + private static final LocTextKey UNEXPECTED_ERROR = new LocTextKey("sebserver.error.unexpected"); private static final String ENTITY_LIST_TYPE = null; private final I18nSupport i18nSupport; @@ -312,6 +313,18 @@ public class PageContextImpl implements PageContext { @Override public void notifyError(final LocTextKey message, final Exception error) { + if (error == null) { + final MessageBox messageBox = new Message( + getShell(), + this.i18nSupport.getText(UNEXPECTED_ERROR), + this.i18nSupport.getText(message), + SWT.ERROR, + this.i18nSupport); + messageBox.setMarkupEnabled(true); + messageBox.open(null); + return; + } + log.error("Unexpected GUI error notified: {}", error.getMessage()); final String errorMessage = message != null diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java index e3b18158..7a76d9ce 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java @@ -59,6 +59,19 @@ public class MonitoringProctoringService { private static final Logger log = LoggerFactory.getLogger(MonitoringProctoringService.class); + public static final LocTextKey OPEN_TOWNHALL_ERROR = + new LocTextKey("sebserver.exam.proctoring.townhall.open.error"); + public static final LocTextKey CLOSE_TOWNHALL_ERROR = + new LocTextKey("sebserver.exam.proctoring.townhall.close.error"); + public static final LocTextKey OPEN_ONE_ERROR = + new LocTextKey("sebserver.exam.proctoring.one.open.error"); + public static final LocTextKey CLOSE_ONE_ERROR = + new LocTextKey("sebserver.exam.proctoring.one.close.error"); + public static final LocTextKey OPEN_COLLECTING_ERROR = + new LocTextKey("sebserver.exam.proctoring.collecting.open.error"); + public static final LocTextKey CLOSE_COLLECTING_ERROR = + new LocTextKey("sebserver.exam.proctoring.collecting.close.error"); + private static final LocTextKey EXAM_ROOM_NAME = new LocTextKey("sebserver.monitoring.exam.proctoring.room.all.name"); @@ -109,26 +122,32 @@ public class MonitoringProctoringService { final PageAction action) { if (isTownhallRoomActive(action.getEntityKey().modelId)) { - closeTownhallRoom(proctoringGUIService, action); - this.pageService.firePageEvent( - new ActionActivationEvent( - true, - new Tuple<>( - ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, - ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)), - action.pageContext()); - return action; + if (closeTownhallRoom(proctoringGUIService, action)) { + this.pageService.firePageEvent( + new ActionActivationEvent( + true, + new Tuple<>( + ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, + ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)), + action.pageContext()); + } else { + action.pageContext().notifyError(CLOSE_TOWNHALL_ERROR, null); + } + } else { - openTownhallRoom(proctoringGUIService, action); - this.pageService.firePageEvent( - new ActionActivationEvent( - true, - new Tuple<>( - ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, - ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)), - action.pageContext()); - return action; + if (openTownhallRoom(proctoringGUIService, action)) { + this.pageService.firePageEvent( + new ActionActivationEvent( + true, + new Tuple<>( + ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, + ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)), + action.pageContext()); + } else { + action.pageContext().notifyError(OPEN_TOWNHALL_ERROR, null); + } } + return action; } public void initCollectingRoomActions( @@ -219,50 +238,56 @@ public class MonitoringProctoringService { final PageAction action, final ClientConnectionData connectionData) { - final String examId = action.getEntityKey().modelId; + try { + final String examId = action.getEntityKey().modelId; - final ProctoringServiceSettings proctoringSettings = this.pageService.getRestService() - .getBuilder(GetProctoringSettings.class) - .withURIVariable(API.PARAM_MODEL_ID, examId) - .call() - .getOrThrow(); - - final Optional roomOptional = - this.pageService.getRestService().getBuilder(GetCollectingRooms.class) - .withURIVariable(API.PARAM_MODEL_ID, examId) - .call() - .getOrThrow() - .stream() - .filter(room -> room.id.equals(connectionData.clientConnection.remoteProctoringRoomId)) - .findFirst(); - - if (roomOptional.isPresent()) { - final RemoteProctoringRoom room = roomOptional.get(); - final ProctoringRoomConnection proctoringConnectionData = this.pageService - .getRestService() - .getBuilder(GetProctorRoomConnection.class) - .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId)) - .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name) - .withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject)) + final ProctoringServiceSettings proctoringSettings = this.pageService.getRestService() + .getBuilder(GetProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, examId) .call() .getOrThrow(); - ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData); - final String script = String.format( - MonitoringProctoringService.OPEN_ROOM_SCRIPT, - room.name, - 800, - 1200, - this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), - this.remoteProctoringEndpoint); + final Optional roomOptional = + this.pageService.getRestService().getBuilder(GetCollectingRooms.class) + .withURIVariable(API.PARAM_MODEL_ID, examId) + .call() + .getOrThrow() + .stream() + .filter(room -> room.id.equals(connectionData.clientConnection.remoteProctoringRoomId)) + .findFirst(); - RWT.getClient() - .getService(JavaScriptExecutor.class) - .execute(script); + if (roomOptional.isPresent()) { + final RemoteProctoringRoom room = roomOptional.get(); + final ProctoringRoomConnection proctoringConnectionData = this.pageService + .getRestService() + .getBuilder(GetProctorRoomConnection.class) + .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId)) + .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name) + .withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject)) + .call() + .getOrThrow(); - this.pageService.getCurrentUser() - .getProctoringGUIService() - .registerProctoringWindow(examId, room.name, room.name); + ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData); + final String script = String.format( + MonitoringProctoringService.OPEN_ROOM_SCRIPT, + room.name, + 800, + 1200, + this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), + this.remoteProctoringEndpoint); + + RWT.getClient() + .getService(JavaScriptExecutor.class) + .execute(script); + + this.pageService.getCurrentUser() + .getProctoringGUIService() + .registerProctoringWindow(examId, room.name, room.name); + } + + } catch (final Exception e) { + log.error("Failed to open popup for collecting room: ", e); + action.pageContext().notifyError(CLOSE_COLLECTING_ERROR, e); } return action; @@ -273,41 +298,49 @@ public class MonitoringProctoringService { final ClientConnectionData connectionData, final ProctoringGUIService proctoringGUIService) { - final String connectionToken = connectionData.clientConnection.connectionToken; - final String examId = action.getEntityKey().modelId; + try { - if (!proctoringGUIService.hasWindow(connectionToken)) { - final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService - .openBreakOutRoom( - examId, - connectionToken, - connectionData.clientConnection.userSessionId, - Arrays.asList(connectionToken)) - .onError(error -> log.error( - "Failed to open single proctoring room for connection {} {}", - connectionToken, - error.getMessage())) - .getOr(null); + final String connectionToken = connectionData.clientConnection.connectionToken; + final String examId = action.getEntityKey().modelId; - ProctoringGUIService.setCurrentProctoringWindowData( - examId, + if (!proctoringGUIService.hasWindow(connectionToken)) { + final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService + .openBreakOutRoom( + examId, + connectionToken, + connectionData.clientConnection.userSessionId, + Arrays.asList(connectionToken)) + .onError(error -> log.error( + "Failed to open single proctoring room for connection {} {}", + connectionToken, + error.getMessage())) + .getOr(null); + + ProctoringGUIService.setCurrentProctoringWindowData( + examId, + connectionToken, + proctoringConnectionData); + } + + final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); + final String script = String.format( + MonitoringProctoringService.OPEN_ROOM_SCRIPT, connectionToken, - proctoringConnectionData); + 420, + 640, + this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), + this.remoteProctoringEndpoint); + javaScriptExecutor.execute(script); + + } catch (final Exception e) { + log.error("Failed to open popup for one to one room: ", e); + action.pageContext().notifyError(OPEN_ONE_ERROR, e); } - final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); - final String script = String.format( - MonitoringProctoringService.OPEN_ROOM_SCRIPT, - connectionToken, - 420, - 640, - this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), - this.remoteProctoringEndpoint); - javaScriptExecutor.execute(script); return action; } - private PageAction openTownhallRoom( + private boolean openTownhallRoom( final ProctoringGUIService proctoringGUIService, final PageAction action) { @@ -342,11 +375,13 @@ public class MonitoringProctoringService { } catch (final Exception e) { log.error("Failed to open popup for town-hall room: ", e); + return false; } - return action; + + return true; } - private PageAction closeTownhallRoom( + private boolean closeTownhallRoom( final ProctoringGUIService proctoringGUIService, final PageAction action) { @@ -360,8 +395,10 @@ public class MonitoringProctoringService { } catch (final Exception e) { log.error("Failed to close proctoring town-hall room for exam: {}", examId); + return false; } - return action; + + return true; } private void updateTownhallButton( @@ -384,16 +421,18 @@ public class MonitoringProctoringService { this.pageService.firePageEvent( new ActionActivationEvent( false, - ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, - ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM), + new Tuple<>( + ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, + ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)), pageContext); } } else { this.pageService.firePageEvent( new ActionActivationEvent( proctoringGUIService.getNumberOfProctoringParticipants() > 0, - ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, - ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM), + new Tuple<>( + ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, + ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)), pageContext); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index aeb8c59d..c8a68897 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -246,7 +246,9 @@ public class ExamAdminServiceImpl implements ExamAdminService { EntityType.EXAM, examId, ProctoringServiceSettings.ATTR_APP_SECRET, - this.cryptor.encrypt(proctoringServiceSettings.appSecret).toString()); + this.cryptor.encrypt(proctoringServiceSettings.appSecret) + .getOrThrow() + .toString()); return proctoringServiceSettings; }); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringRoomService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringRoomService.java index 8b07bc83..dba2e0e5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringRoomService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringRoomService.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import java.util.Collection; import java.util.Map; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; @@ -75,6 +76,8 @@ public interface ExamProctoringRoomService { * @return Result refer to the RemoteProctoringRoom data or to an error when happened */ Result getTownhallRoomData(final Long examId); + Result closeTownhallRoom(Long examId); + /** Used to create a break out room for all active SEB clients given by the connectionTokens. * This first notifies the underling proctoring specific service layer on room creation that will create a room * on the meeting service if necessary. Then creating the room internally for holding data and tracking the new diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java index 5431e3a5..16b8da96 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java @@ -22,6 +22,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; @@ -323,6 +324,16 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService return this.remoteProctoringRoomDAO.isTownhallRoomActive(examId); } + @Override + public Result closeTownhallRoom(final Long examId) { + if (isTownhallRoomActive(examId)) { + return this.remoteProctoringRoomDAO.getTownhallRoom(examId) + .flatMap(room -> this.remoteProctoringRoomDAO.deleteRoom(room.id)); + } + + return Result.ofRuntimeError("No active town-hall for exam: " + examId); + } + private void closeTownhall( final Long examId, final ProctoringServiceSettings proctoringSettings, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java index 5498a550..52d14ec3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java @@ -286,6 +286,9 @@ public class ExamProctoringController { checkAccess(institutionId, examId); return this.examProcotringRoomService .openTownhallRoom(examId, subject) + .onError(error -> this.examProcotringRoomService.closeTownhallRoom(examId) + .onError(err -> log.error("Failed to close town-hall after failed opening: {}", + err.getMessage()))) .getOrThrow(); } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 10eccd7d..66454897 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -665,6 +665,13 @@ sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet se sebserver.exam.proctoring.type.servertype.ZOOM=Zoom Server sebserver.exam.proctoring.type.servertype.ZOOM.tooltip=Use a Zoom meeting server for proctoring +sebserver.exam.proctoring.townhall.open.error=Failed to open the town-hall room. +sebserver.exam.proctoring.townhall.close.error=Failed to close the town-hall room properly. +sebserver.exam.proctoring.one.open.error=Failed to open the one-to-one room. +sebserver.exam.proctoring.one.close.error=Failed to close the one-to-one room properly. +sebserver.exam.proctoring.collecting.open.error=Failed to open the collecting room. +sebserver.exam.proctoring.collecting.close.error=Failed to close the collecting room properly. + ################################ # Connection Configuration