From cecce64ba096eaf8466ecdbce50e5887d50b03c7 Mon Sep 17 00:00:00 2001 From: Nadim Ritter Date: Wed, 21 Feb 2024 16:10:59 +0100 Subject: [PATCH] SEBSLI-9 create new endpoint for light setup to stream exam config --- .../ethz/seb/sebserver/WebSecurityConfig.java | 1 + .../ch/ethz/seb/sebserver/gbl/api/API.java | 2 + .../session/SEBClientConnectionService.java | 5 + .../impl/SEBClientConnectionServiceImpl.java | 97 +++++++++++++------ .../api/ExamAPIDiscoveryLightController.java | 95 ++++++++++++++++++ 5 files changed, 173 insertions(+), 27 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIDiscoveryLightController.java diff --git a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java index d627365a..56f97369 100644 --- a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java @@ -84,6 +84,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E .antMatchers(ERROR_PATH) .antMatchers(CHECK_PATH) .antMatchers(this.examAPIDiscoveryEndpoint) + .antMatchers(this.examAPIDiscoveryEndpoint + API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT) .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**") .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**") .antMatchers(this.adminAPIEndpoint + API.REGISTER_ENDPOINT); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 5f963fe7..124d6979 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -123,6 +123,8 @@ public final class API { public static final String EXAM_API_CONFIGURATION_REQUEST_ENDPOINT = "/examconfig"; + public static final String EXAM_API_CONFIGURATION_LIGHT_ENDPOINT = "/light-config"; + public static final String EXAM_API_PING_ENDPOINT = "/sebping"; public static final String EXAM_API_PING_TIMESTAMP = "timestamp"; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java index c28fe531..991df767 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.security.Principal; import java.util.Collection; @@ -173,4 +174,8 @@ public interface SEBClientConnectionService { String ipAddress, HttpServletResponse response); + void streamLightExamConfig( + String modelId, + HttpServletResponse response) throws IOException; + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java index f3f064bb..c02c6d06 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java @@ -8,23 +8,36 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import java.security.Principal; -import java.util.*; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.Entity; +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.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.VDIType; +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.ClientInstruction; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; +import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService; +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.SEBClientInstructionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedIndicatorValueService; +import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -34,25 +47,20 @@ import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; 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.sebconfig.SEBClientConfig; -import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.VDIType; -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.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.gbl.util.Result; -import ch.ethz.seb.sebserver.webservice.WebserviceInfo; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; -import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; -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.impl.indicator.DistributedIndicatorValueService; -import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Lazy @Service @@ -79,6 +87,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic private final SecurityKeyService securityKeyService; private final SEBClientEventBatchService sebClientEventBatchService; private final SEBClientInstructionService sebClientInstructionService; + private final ClientConfigService clientConfigService; private final JSONMapper jsonMapper; private final boolean isDistributedSetup; @@ -92,6 +101,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic final WebserviceInfo webserviceInfo, final SEBClientEventBatchService sebClientEventBatchService, final SEBClientInstructionService sebClientInstructionService, + final ClientConfigService clientConfigService, final JSONMapper jsonMapper) { this.examSessionService = examSessionService; @@ -105,6 +115,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic this.isDistributedSetup = webserviceInfo.isDistributed(); this.sebClientEventBatchService = sebClientEventBatchService; this.sebClientInstructionService = sebClientInstructionService; + this.clientConfigService = clientConfigService; this.jsonMapper = jsonMapper; } @@ -623,6 +634,38 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic } } + public void streamLightExamConfig(final String modelId, final HttpServletResponse response) throws IOException{ + + final ServletOutputStream outputStream = response.getOutputStream(); + PipedOutputStream pout; + PipedInputStream pin; + + try { + pout = new PipedOutputStream(); + pin = new PipedInputStream(pout); + + this.clientConfigService.exportSEBClientConfiguration( + pout, + modelId, + null); + + IOUtils.copyLarge(pin, outputStream); + + response.setStatus(HttpStatus.OK.value()); + + outputStream.flush(); + + }catch(Exception e){ + final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); + outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + + } finally { + outputStream.flush(); + outputStream.close(); + } + } + private void writeSEBClientErrors( final HttpServletResponse response, final Collection errorMessages) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIDiscoveryLightController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIDiscoveryLightController.java new file mode 100644 index 00000000..09aab832 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIDiscoveryLightController.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 ETH Zürich, IT Services / Informatikdienste (ID) + * + * 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.weblayer.api; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +@WebServiceProfile +@RestController +@RequestMapping("${sebserver.webservice.api.exam.endpoint.discovery}") +@ConditionalOnExpression("'${sebserver.webservice.light.setup}'.equals('true')") +public class ExamAPIDiscoveryLightController { + + private final SEBClientConnectionService sebClientConnectionService; + private final SEBClientConfigDAO sebClientConfigDAO; + private final Executor executor; + + protected ExamAPIDiscoveryLightController( + final SEBClientConnectionService sebClientConnectionService, + final SEBClientConfigDAO sebClientConfigDAO, + @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) final Executor executor) { + + this.sebClientConnectionService = sebClientConnectionService; + this.sebClientConfigDAO = sebClientConfigDAO; + this.executor = executor; + } + + //this.examAPI_V1_Endpoint + API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT + //http://localhost:8080/exam-api/discovery/light-config + @RequestMapping( + path = API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT, + method = RequestMethod.GET, + produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public CompletableFuture getLightConfig( + final HttpServletRequest request, + final HttpServletResponse response){ + + //temp solution: get first active seb client config we can get --> + //in a light setup there should be only one setup so this step is not necessary and we can just use the first and only item in the db + String modelId = getSebClientConfigId(); + + return CompletableFuture.runAsync( + () -> { + try { + this.sebClientConnectionService.streamLightExamConfig(modelId, response); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, + this.executor + ); + + } + + private String getSebClientConfigId() { + return this.sebClientConfigDAO + .allMatching( + new FilterMap().putIfAbsent( + "active", + String.valueOf(true) + ), + Utils.truePredicate() + ) + .getOrThrow() + .stream() + .collect(Collectors.toList()) + .get(0) + .getModelId(); + } + +}