From 44688429bb5e2237706153f7a24bc1197a1c424c Mon Sep 17 00:00:00 2001 From: anhefti Date: Fri, 28 Jun 2019 12:46:36 +0200 Subject: [PATCH] SEBSERV-62 service implementations and preparing for config download --- .../servicelayer/dao/ClientConnectionDAO.java | 3 + .../dao/impl/ClientConnectionDAOImpl.java | 31 +++ .../sebconfig/SebConfigEncryptionService.java | 5 + .../sebconfig/SebExamConfigService.java | 8 + .../sebconfig/XMLValueConverter.java | 14 +- .../sebconfig/impl/ExamConfigIO.java | 132 +++++++++++ .../impl/SebConfigEncryptionServiceImpl.java | 2 - .../impl/SebExamConfigServiceImpl.java | 145 ++++-------- .../impl/XMLValueConverterServiceImpl.java | 71 ++++++ .../converter/ArrayOfStringConverter.java | 4 +- .../impl/converter/BooleanConverter.java | 4 +- .../impl/converter/IntegerConverter.java | 4 +- .../impl/converter/KioskModeConverter.java | 4 +- .../impl/converter/StringConverter.java | 4 +- .../impl/converter/TableConverter.java | 13 +- .../session/ExamSessionService.java | 22 +- .../session/SebClientConnectionService.java | 12 +- .../session/impl/ExamSessionCacheService.java | 27 ++- .../session/impl/ExamSessionServiceImpl.java | 47 +--- .../session/impl/InMemorySebConfig.java | 67 ++++++ .../impl/SebClientConnectionServiceImpl.java | 206 ++++++++++++++++++ 21 files changed, 644 insertions(+), 181 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/XMLValueConverterServiceImpl.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySebConfig.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebClientConnectionServiceImpl.java diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java index f5ed896e..ff52c5dc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java @@ -9,7 +9,10 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.util.Result; public interface ClientConnectionDAO extends EntityDAO { + Result byConnectionToken(Long institutionId, String connectionToken); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java index 1c10671e..3f263522 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java @@ -183,6 +183,37 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO { }); } + @Override + @Transactional(readOnly = true) + public Result byConnectionToken( + final Long institutionId, + final String connectionToken) { + + return Result.tryCatch(() -> { + final List list = this.clientConnectionRecordMapper + .selectByExample() + .where( + ClientConnectionRecordDynamicSqlSupport.institutionId, + SqlBuilder.isEqualTo(institutionId)) + .and( + ClientConnectionRecordDynamicSqlSupport.connectionToken, + SqlBuilder.isEqualTo(connectionToken)) + .build() + .execute(); + + if (list.isEmpty()) { + throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, "connectionToken"); + } + + if (list.size() > 1) { + throw new IllegalStateException("Only one ClientConnection expected but there are: " + list.size()); + } + + return list.get(0); + }) + .flatMap(ClientConnectionDAOImpl::toDomainModel); + } + private Result recordById(final Long id) { return Result.tryCatch(() -> { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebConfigEncryptionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebConfigEncryptionService.java index 8077e231..2ab2ee1d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebConfigEncryptionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebConfigEncryptionService.java @@ -14,6 +14,9 @@ import java.security.cert.Certificate; import java.util.function.Function; import java.util.function.Supplier; +import org.springframework.scheduling.annotation.Async; + +import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.util.Utils; /** Used for SEB Configuration encryption and decryption */ @@ -58,6 +61,7 @@ public interface SebConfigEncryptionService { * @param output the output data stream to write the cipher text to * @param input the input stream to read the plain text from * @param context the SebConfigEncryptionContext to access strategy specific data needed for encryption */ + @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) void streamEncrypted( final OutputStream output, final InputStream input, @@ -68,6 +72,7 @@ public interface SebConfigEncryptionService { * @param output the output data stream to write encrypted plain text to * @param input the input stream to read the cipher text from * @param context the SebConfigEncryptionContext to access strategy specific data needed for encryption */ + @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) void streamDecrypted( final OutputStream output, final InputStream input, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java index 19c49477..316130a6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java @@ -48,6 +48,14 @@ public interface SebExamConfigService { * @param configExamMappingId The identifier of the Exam Configuration Mapping */ void exportForExam(OutputStream out, Long configExamMappingId); + /** Used to export the default SEB Exam Configuration for a given exam identifier. + * either with encryption if defined or as plain text within the SEB Configuration format + * as described here: https://www.safeexambrowser.org/developer/seb-file-format.html + * + * @param out The output stream to write the export data to + * @param examId the exam identifier */ + void exportDefaultForExam(OutputStream out, Long examId); + /** TODO */ String generateConfigKey(Long configurationNodeId); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java index baf8b650..f083e959 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java @@ -22,7 +22,7 @@ public interface XMLValueConverter { /** Gives a Set of AttributeType's a concrete converter is able to * handle and convert ConfigurationValue of attributes of given types. - * + * * @return a Set of supported AttributeType's of the converter */ Set types(); @@ -35,7 +35,7 @@ public interface XMLValueConverter { /** Used to convert the a given ConfigurationAttribute / ConfigurationValue * pair to plain XML text for block of this SEB Configuration attribute. - * + * * @param out The output stream to write the plain XML text block to * @param attribute The ConfigurationAttribute containing all attribute information * @param value The ConfigurationValue containing the value @@ -44,8 +44,7 @@ public interface XMLValueConverter { void convertToXML( OutputStream out, ConfigurationAttribute attribute, - ConfigurationValue value, - XMLValueConverterService xmlValueConverterService) throws IOException; + ConfigurationValue value) throws IOException; default String extractName(final ConfigurationAttribute attribute) { final int lastIndexOf = attribute.name.lastIndexOf('.'); @@ -56,4 +55,11 @@ public interface XMLValueConverter { } } + /** This can be overwritten if a XMLValueConverter needs the XMLValueConverterService. + * The XMLValueConverterService is then injected by its self on initialization. + * + * @param xmlValueConverterService */ + default void init(final XMLValueConverterService xmlValueConverterService) { + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java new file mode 100644 index 00000000..a53ec8c5 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2019 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.sebconfig.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; + +@Lazy +@Component +@WebServiceProfile +public class ExamConfigIO { + + private static final Logger log = LoggerFactory.getLogger(ExamConfigIO.class); + + private final ConfigurationAttributeDAO configurationAttributeDAO; + private final ConfigurationValueDAO configurationValueDAO; + private final ConfigurationDAO configurationDAO; + private final XMLValueConverterService xmlValueConverterService; + + protected ExamConfigIO( + final ConfigurationAttributeDAO configurationAttributeDAO, + final ConfigurationValueDAO configurationValueDAO, + final ConfigurationDAO configurationDAO, + final XMLValueConverterService xmlValueConverterService) { + + this.configurationAttributeDAO = configurationAttributeDAO; + this.configurationValueDAO = configurationValueDAO; + this.configurationDAO = configurationDAO; + this.xmlValueConverterService = xmlValueConverterService; + } + + @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) + void exportPlainXML(final OutputStream out, final Long institutionId, final Long configurationNodeId) { + // get all defined root configuration attributes + final Map attributes = this.configurationAttributeDAO.getAllRootAttributes() + .getOrThrow() + .stream() + .collect(Collectors.toMap( + ConfigurationAttribute::getId, + Function.identity())); + + final List sortedAttributes = attributes + .values() + .stream() + .sorted() + .collect(Collectors.toList()); + + // get follow-up configurationId for given configurationNodeId + final Long configurationId = this.configurationDAO + .getFollowupConfiguration(configurationNodeId) + .getOrThrow().id; + + // get all values for that attributes for given configurationId + final Map values = this.configurationValueDAO + .allRootAttributeValues(institutionId, configurationId) + .getOrThrow() + .stream() + .collect(Collectors.toMap( + ConfigurationValue::getAttributeId, + Function.identity())); + + try { + // write headers + out.write(Constants.XML_VERSION_HEADER_UTF_8); + out.write(Constants.XML_DOCTYPE_HEADER_UTF_8); + + // plist open + out.write(Constants.XML_PLIST_START_V1_UTF_8); + out.write(Constants.XML_DICT_START_UTF_8); + + // write attributes + for (final ConfigurationAttribute attribute : sortedAttributes) { + final ConfigurationValue configurationValue = values.get(attribute.id); + if (configurationValue != null) { + this.xmlValueConverterService.getXMLConverter(attribute).convertToXML( + out, + attribute, + configurationValue); + } + } + + // plist close + out.write(Constants.XML_DICT_END_UTF_8); + out.write(Constants.XML_PLIST_END_UTF_8); + out.flush(); + + } catch (final IOException e) { + log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e); + try { + out.flush(); + } catch (final IOException e1) { + log.error("Unable to flush output stream after error"); + } + } finally { + IOUtils.closeQuietly(out); + } + } + + @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) + void importPlainXML(final InputStream in, final Long institutionId, final Long configurationNodeId) { + // TODO version 1 + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebConfigEncryptionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebConfigEncryptionServiceImpl.java index d0d1216c..2002eca5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebConfigEncryptionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebConfigEncryptionServiceImpl.java @@ -82,7 +82,6 @@ public final class SebConfigEncryptionServiceImpl implements SebConfigEncryption pin.close(); pout.flush(); pout.close(); - output.flush(); } catch (final IOException e) { log.error("Error while stream encrypted data: ", e); @@ -135,7 +134,6 @@ public final class SebConfigEncryptionServiceImpl implements SebConfigEncryption pin.close(); pout.flush(); pout.close(); - output.flush(); } catch (final IOException e) { log.error("Error while stream decrypted data: ", e); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java index d64d577e..0d0894ac 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java @@ -10,89 +10,45 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; import java.io.IOException; import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; -import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Service @WebServiceProfile -public class SebExamConfigServiceImpl implements SebExamConfigService, XMLValueConverterService { +public class SebExamConfigServiceImpl implements SebExamConfigService { private static final Logger log = LoggerFactory.getLogger(SebExamConfigServiceImpl.class); + private final ExamConfigIO examConfigIO; private final ConfigurationAttributeDAO configurationAttributeDAO; - private final ConfigurationValueDAO configurationValueDAO; - private final ConfigurationDAO configurationDAO; private final Collection validators; - private final Map convertersByAttributeName; - private final Map convertersByAttributeType; protected SebExamConfigServiceImpl( + final ExamConfigIO examConfigIO, final ConfigurationAttributeDAO configurationAttributeDAO, - final ConfigurationValueDAO configurationValueDAO, - final ConfigurationDAO configurationDAO, - final Collection validators, - final Collection converters) { + final Collection validators) { + this.examConfigIO = examConfigIO; this.configurationAttributeDAO = configurationAttributeDAO; - this.configurationValueDAO = configurationValueDAO; - this.configurationDAO = configurationDAO; this.validators = validators; - this.convertersByAttributeName = new HashMap<>(); - this.convertersByAttributeType = new HashMap<>(); - for (final XMLValueConverter converter : converters) { - if (StringUtils.isNotBlank(converter.name())) { - this.convertersByAttributeName.put(converter.name(), converter); - } - for (final AttributeType aType : converter.types()) { - if (this.convertersByAttributeType.containsKey(aType)) { - log.warn( - "Unexpected state in inititalization: A XMLValueConverter for AttributeType {} exists already: {}", - aType, - converter); - } - this.convertersByAttributeType.put(aType, converter); - } - } - } - - @Override - public XMLValueConverter getXMLConverter(final ConfigurationAttribute attribute) { - if (this.convertersByAttributeName.containsKey(attribute.name)) { - return this.convertersByAttributeName.get(attribute.name); - } - - if (this.convertersByAttributeType.containsKey(attribute.type)) { - return this.convertersByAttributeType.get(attribute.type); - } - - throw new IllegalStateException("No XMLValueConverter found for attribute: " + attribute); } @Override @@ -125,68 +81,45 @@ public class SebExamConfigServiceImpl implements SebExamConfigService, XMLValueC final Long institutionId, final Long configurationNodeId) { - // get all defined root configuration attributes - final Map attributes = this.configurationAttributeDAO.getAllRootAttributes() - .getOrThrow() - .stream() - .collect(Collectors.toMap( - ConfigurationAttribute::getId, - Function.identity())); - - final List sortedAttributes = attributes - .values() - .stream() - .sorted() - .collect(Collectors.toList()); - - // get follow-up configurationId for given configurationNodeId - final Long configurationId = this.configurationDAO - .getFollowupConfiguration(configurationNodeId) - .getOrThrow().id; - - // get all values for that attributes for given configurationId - final Map values = this.configurationValueDAO - .allRootAttributeValues(institutionId, configurationId) - .getOrThrow() - .stream() - .collect(Collectors.toMap( - ConfigurationValue::getAttributeId, - Function.identity())); + if (log.isDebugEnabled()) { + log.debug("Start to stream plain text SEB clonfiguration data"); + } + PipedOutputStream pout = null; + PipedInputStream pin = null; try { - // write headers - out.write(Constants.XML_VERSION_HEADER_UTF_8); - out.write(Constants.XML_DOCTYPE_HEADER_UTF_8); + pout = new PipedOutputStream(); + pin = new PipedInputStream(pout); - // plist open - out.write(Constants.XML_PLIST_START_V1_UTF_8); - out.write(Constants.XML_DICT_START_UTF_8); + this.examConfigIO.exportPlainXML(pout, institutionId, configurationNodeId); - // write attributes - for (final ConfigurationAttribute attribute : sortedAttributes) { - final ConfigurationValue configurationValue = values.get(attribute.id); - if (configurationValue != null) { - this.getXMLConverter(attribute).convertToXML( - out, - attribute, - configurationValue, - this); - } - } + IOUtils.copyLarge(pin, out); - // plist close - out.write(Constants.XML_DICT_END_UTF_8); - out.write(Constants.XML_PLIST_END_UTF_8); - out.flush(); + pin.close(); + pout.flush(); + pout.close(); } catch (final IOException e) { - log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e); + log.error("Error while stream plain text SEB clonfiguration data: ", e); + } finally { try { - out.flush(); + if (pin != null) + pin.close(); } catch (final IOException e1) { - log.error("Unable to flush output stream after error"); + log.error("Failed to close PipedInputStream: ", e1); + } + try { + if (pout != null) + pout.close(); + } catch (final IOException e1) { + log.error("Failed to close PipedOutputStream: ", e1); + } + + if (log.isDebugEnabled()) { + log.debug("Finished to stream plain text SEB clonfiguration data"); } } + } @Override @@ -195,6 +128,12 @@ public class SebExamConfigServiceImpl implements SebExamConfigService, XMLValueC } + @Override + public void exportDefaultForExam(final OutputStream out, final Long examId) { + // TODO Auto-generated method stub + + } + @Override public String generateConfigKey(final Long configurationNodeId) { // TODO https://www.safeexambrowser.org/developer/seb-config-key.html diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/XMLValueConverterServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/XMLValueConverterServiceImpl.java new file mode 100644 index 00000000..1284f57a --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/XMLValueConverterServiceImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 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.sebconfig.impl; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; + +@Lazy +@Service +@WebServiceProfile +public class XMLValueConverterServiceImpl implements XMLValueConverterService { + + private static final Logger log = LoggerFactory.getLogger(XMLValueConverterServiceImpl.class); + + private final Map convertersByAttributeName; + private final Map convertersByAttributeType; + + public XMLValueConverterServiceImpl(final Collection converters) { + this.convertersByAttributeName = new HashMap<>(); + this.convertersByAttributeType = new HashMap<>(); + for (final XMLValueConverter converter : converters) { + converter.init(this); + if (StringUtils.isNotBlank(converter.name())) { + this.convertersByAttributeName.put(converter.name(), converter); + } + + for (final AttributeType aType : converter.types()) { + if (this.convertersByAttributeType.containsKey(aType)) { + log.warn( + "Unexpected state in inititalization: A XMLValueConverter for AttributeType {} exists already: {}", + aType, + converter); + } + this.convertersByAttributeType.put(aType, converter); + } + } + } + + @Override + public XMLValueConverter getXMLConverter(final ConfigurationAttribute attribute) { + if (this.convertersByAttributeName.containsKey(attribute.name)) { + return this.convertersByAttributeName.get(attribute.name); + } + + if (this.convertersByAttributeType.containsKey(attribute.type)) { + return this.convertersByAttributeType.get(attribute.type); + } + + throw new IllegalStateException("No XMLValueConverter found for attribute: " + attribute); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java index 3e64e397..b7676a5e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java @@ -26,7 +26,6 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Component @@ -58,8 +57,7 @@ public class ArrayOfStringConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value, - final XMLValueConverterService xmlValueConverterService) throws IOException { + final ConfigurationValue value) throws IOException { final String val = (value.value != null) ? value.value : attribute.getDefaultValue(); if (StringUtils.isNotBlank(val)) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java index 9e761ea7..960c69cd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java @@ -26,7 +26,6 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Component @@ -53,8 +52,7 @@ public class BooleanConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value, - final XMLValueConverterService xmlValueConverterService) throws IOException { + final ConfigurationValue value) throws IOException { out.write(Utils.toByteArray( String.format( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java index 2dc4c678..40d0ad88 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java @@ -25,7 +25,6 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Component @@ -56,8 +55,7 @@ public class IntegerConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value, - final XMLValueConverterService xmlValueConverterService) throws IOException { + final ConfigurationValue value) throws IOException { final String val = (value.value != null) ? value.value : attribute.getDefaultValue(); if (StringUtils.isNotBlank(val)) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java index 842f02df..a21d541d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java @@ -23,7 +23,6 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Component @@ -48,8 +47,7 @@ public class KioskModeConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value, - final XMLValueConverterService xmlValueConverterService) throws IOException { + final ConfigurationValue value) throws IOException { final String val = value.getValue(); out.write(Utils.toByteArray( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java index d8df12ea..834a049e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java @@ -25,7 +25,6 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Component @@ -57,8 +56,7 @@ public class StringConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value, - final XMLValueConverterService xmlValueConverterService) throws IOException { + final ConfigurationValue value) throws IOException { final String val = (value.value != null) ? value.value : attribute.getDefaultValue(); if (StringUtils.isNotBlank(val)) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java index 9e6e44ed..dee3ed15 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java @@ -54,6 +54,7 @@ public class TableConverter implements XMLValueConverter { private final ConfigurationAttributeDAO configurationAttributeDAO; private final ConfigurationValueDAO configurationValueDAO; + private XMLValueConverterService xmlValueConverterService; public TableConverter( final ConfigurationAttributeDAO configurationAttributeDAO, @@ -63,6 +64,11 @@ public class TableConverter implements XMLValueConverter { this.configurationValueDAO = configurationValueDAO; } + @Override + public void init(final XMLValueConverterService xmlValueConverterService) { + this.xmlValueConverterService = xmlValueConverterService; + } + @Override public Set types() { return SUPPORTED_TYPES; @@ -77,8 +83,7 @@ public class TableConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value, - final XMLValueConverterService xmlValueConverterService) throws IOException { + final ConfigurationValue value) throws IOException { out.write(Utils.toByteArray(String.format(KEY_TEMPLATE, extractName(attribute)))); @@ -101,7 +106,7 @@ public class TableConverter implements XMLValueConverter { out, getAttributes(attribute), values, - xmlValueConverterService); + this.xmlValueConverterService); if (attribute.type != AttributeType.COMPOSITE_TABLE) { out.write(ARRAY_END); @@ -134,7 +139,7 @@ public class TableConverter implements XMLValueConverter { for (final ConfigurationValue value : rowValues) { final ConfigurationAttribute attr = attributeMap.get(value.attributeId); final XMLValueConverter converter = xmlValueConverterService.getXMLConverter(attr); - converter.convertToXML(out, attr, value, xmlValueConverterService); + converter.convertToXML(out, attr, value); } out.write(DICT_END); out.flush(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java index 4545a752..626f6e2f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java @@ -11,19 +11,31 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import java.util.Collection; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.util.Result; +/** A Service to handle running exam sessions */ public interface ExamSessionService { + /** Indicates whether an Exam is currently running or not. + * + * @param examId the PK of the Exam to test + * @return true if an Exam is currently running */ boolean isExamRunning(Long examId); + /** Use this to get currently running exams by exam identifier. + * This test first if the Exam with the given identifier is currently/still + * running. If true the Exam is returned within the result. Otherwise the + * returned Result contains a NoSuchElementException error. + * + * @param examId The Exam identifier (PK) + * @return Result referencing the running Exam or an error if the Exam not exists or is not currently running */ Result getRunningExam(Long examId); + /** Gets all all currently running Exams for a particular Institution. + * + * @param institutionId the Institution identifier + * @return Result referencing the list of all currently running Exams of the institution or to an error if + * happened. */ Result> getRunningExamsForInstitution(Long institutionId); - void notifyPing(Long connectionId, long timestamp, int pingNumber); - - void notifyClientEvent(final ClientEvent event, Long connectionId); - } 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 fb47953b..dd5b63aa 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,17 +9,27 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.util.Result; public interface SebClientConnectionService { - Result createClientConnection(Long instituionId, Long examId); + Result createClientConnection( + Long institutionId, + String clientAddress, + Long examId); Result establishClientConnection( + final Long institutionId, String connectionToken, Long examId, + final String clientAddress, String userSessionId); Result closeConnection(String connectionToken); + void notifyPing(Long connectionId, long timestamp, int pingNumber); + + void notifyClientEvent(final ClientEvent event, Long connectionId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java index 3fa94972..e0dc8aa2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java @@ -21,6 +21,7 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService; @Lazy @Service @@ -29,21 +30,25 @@ public class ExamSessionCacheService { public static final String CACHE_NAME_RUNNING_EXAM = "RUNNING_EXAM"; public static final String CACHE_NAME_ACTIVE_CLIENT_CONNECTION = "ACTIVE_CLIENT_CONNECTION"; + public static final String CACHE_NAME_SEB_CONFIG_EXAM = "SEB_CONFIG_EXAM"; private static final Logger log = LoggerFactory.getLogger(ExamSessionCacheService.class); private final ExamDAO examDAO; private final ClientConnectionDAO clientConnectionDAO; private final ClientIndicatorFactory clientIndicatorFactory; + private final SebExamConfigService sebExamConfigService; protected ExamSessionCacheService( final ExamDAO examDAO, final ClientConnectionDAO clientConnectionDAO, - final ClientIndicatorFactory clientIndicatorFactory) { + final ClientIndicatorFactory clientIndicatorFactory, + final SebExamConfigService sebExamConfigService) { this.examDAO = examDAO; this.clientConnectionDAO = clientConnectionDAO; this.clientIndicatorFactory = clientIndicatorFactory; + this.sebExamConfigService = sebExamConfigService; } @Cacheable( @@ -123,10 +128,28 @@ public class ExamSessionCacheService { @CacheEvict( cacheNames = CACHE_NAME_ACTIVE_CLIENT_CONNECTION, key = "#connectionId") - void evict(final Long connectionId) { + void evictClientConnection(final Long connectionId) { if (log.isDebugEnabled()) { log.debug("Eviction of ClientConnectionData from cache: {}", connectionId); } } + @Cacheable( + cacheNames = CACHE_NAME_SEB_CONFIG_EXAM, + key = "#examId", + unless = "#result == null") + InMemorySebConfig getDefaultSebConfigForExam(final Long examId) { + // TODO + return null; + } + + @CacheEvict( + cacheNames = CACHE_NAME_SEB_CONFIG_EXAM, + key = "#examId") + void evictDefaultSebConfig(final Long examId) { + if (log.isDebugEnabled()) { + log.debug("Eviction of default SEB Configuration from cache for exam: {}", examId); + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 37c94e02..3ed157ee 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -12,19 +12,15 @@ import java.util.Collection; import java.util.NoSuchElementException; import java.util.stream.Collectors; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Lazy; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; -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.ExamDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrategy; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; -import io.micrometer.core.instrument.util.StringUtils; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SebClientConnectionService; @Lazy @Service @@ -33,27 +29,14 @@ public class ExamSessionServiceImpl implements ExamSessionService { private final ExamSessionCacheService examSessionCacheService; private final ExamDAO examDAO; - private final EventHandlingStrategy eventHandlingStrategy; protected ExamSessionServiceImpl( final ExamSessionCacheService examSessionCacheService, final ExamDAO examDAO, - final Environment environment, - final ApplicationContext applicationContext) { + final SebClientConnectionService sebClientConnectionService) { this.examSessionCacheService = examSessionCacheService; this.examDAO = examDAO; - - String eventHandlingStrategyProperty = - environment.getProperty(EventHandlingStrategy.EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY); - - if (StringUtils.isBlank(eventHandlingStrategyProperty)) { - eventHandlingStrategyProperty = EventHandlingStrategy.EVENT_CONSUMER_STRATEGY_SINGLE_EVENT_STORE; - } - - this.eventHandlingStrategy = applicationContext.getBean( - eventHandlingStrategyProperty, - EventHandlingStrategy.class); } @Override @@ -85,30 +68,4 @@ public class ExamSessionServiceImpl implements ExamSessionService { .collect(Collectors.toList())); } - @Override - public void notifyPing(final Long connectionId, final long timestamp, final int pingNumber) { - final ClientConnectionDataInternal activeClientConnection = - this.examSessionCacheService.getActiveClientConnection(connectionId); - - if (activeClientConnection != null) { - activeClientConnection.pingMappings - .stream() - .forEach(pingIndicator -> pingIndicator.notifyPing(timestamp, pingNumber)); - } - } - - @Override - public void notifyClientEvent(final ClientEvent event, final Long connectionId) { - this.eventHandlingStrategy.accept(event); - - final ClientConnectionDataInternal activeClientConnection = - this.examSessionCacheService.getActiveClientConnection(connectionId); - - if (activeClientConnection != null) { - activeClientConnection.getindicatorMapping(event.eventType) - .stream() - .forEach(indicator -> indicator.notifyValueChange(event)); - } - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySebConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySebConfig.java new file mode 100644 index 00000000..bf93d2a8 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySebConfig.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 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; + +public final class InMemorySebConfig { + + public final Long configId; + public final Long examId; + private final byte[] data; + + protected InMemorySebConfig(final Long configId, final Long examId, final byte[] data) { + super(); + this.configId = configId; + this.examId = examId; + this.data = data; + } + + public Long getConfigId() { + return this.configId; + } + + public Long getExamId() { + return this.examId; + } + + public byte[] getData() { + return this.data; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.configId == null) ? 0 : this.configId.hashCode()); + result = prime * result + ((this.examId == null) ? 0 : this.examId.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final InMemorySebConfig other = (InMemorySebConfig) obj; + if (this.configId == null) { + if (other.configId != null) + return false; + } else if (!this.configId.equals(other.configId)) + return false; + if (this.examId == null) { + if (other.examId != null) + return false; + } else if (!this.examId.equals(other.examId)) + return false; + return true; + } + +} 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 new file mode 100644 index 00000000..4615cec1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebClientConnectionServiceImpl.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2019 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.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; + +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrategy; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SebClientConnectionService; +import io.micrometer.core.instrument.util.StringUtils; + +public class SebClientConnectionServiceImpl implements SebClientConnectionService { + + private static final Logger log = LoggerFactory.getLogger(SebClientConnectionServiceImpl.class); + + private final ExamSessionService examSessionService; + private final ExamSessionCacheService examSessionCacheService; + private final EventHandlingStrategy eventHandlingStrategy; + private final ClientConnectionDAO clientConnectionDAO; + + protected SebClientConnectionServiceImpl( + final ExamSessionService examSessionService, + final ExamSessionCacheService examSessionCacheService, + final ClientConnectionDAO clientConnectionDAO, + final Environment environment, + final ApplicationContext applicationContext) { + + this.examSessionService = examSessionService; + this.examSessionCacheService = examSessionCacheService; + this.clientConnectionDAO = clientConnectionDAO; + + String eventHandlingStrategyProperty = + environment.getProperty(EventHandlingStrategy.EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY); + + if (StringUtils.isBlank(eventHandlingStrategyProperty)) { + eventHandlingStrategyProperty = EventHandlingStrategy.EVENT_CONSUMER_STRATEGY_SINGLE_EVENT_STORE; + } + + this.eventHandlingStrategy = applicationContext.getBean( + eventHandlingStrategyProperty, + EventHandlingStrategy.class); + } + + @Override + public Result createClientConnection( + final Long institutionId, + final String clientAddress, + final Long examId) { + + return Result.tryCatch(() -> { + + if (log.isDebugEnabled()) { + log.debug("SEB client connection attempt, create ClientConnection for instituion {} and exam: {}", + institutionId, + examId); + } + + // Integrity check: in case examId is provided is the specified exam running? + if (examId != null && !this.examSessionService.isExamRunning(examId)) { + examNotRunningException(examId); + } + + // Create ClientConnection in status CONNECTION_REQUESTED for further processing + final String connectionToken = createToken(); + final ClientConnection clientConnection = this.clientConnectionDAO.createNew(new ClientConnection( + null, + institutionId, + examId, + ClientConnection.ConnectionStatus.CONNECTION_REQUESTED, + connectionToken, + null, + clientAddress, + null)) + .getOrThrow(); + + if (log.isDebugEnabled()) { + log.debug("New ClientConnection created: {}", clientConnection); + } + + return clientConnection; + }); + } + + @Override + public Result establishClientConnection( + final Long institutionId, + final String connectionToken, + final Long examId, + final String clientAddress, + final String userSessionId) { + + return Result.tryCatch(() -> { + + if (log.isDebugEnabled()) { + log.debug( + "SEB client connection, establish ClientConnection for instituion {} and exam: {} and userSessionId: {}", + institutionId, + examId, + userSessionId); + } + + // Integrity check: is the specified exam running? + if (!this.examSessionService.isExamRunning(examId)) { + examNotRunningException(examId); + } + + final ClientConnection clientConnection = this.clientConnectionDAO + .byConnectionToken(institutionId, connectionToken) + .get(t -> { + // TODO: This indicates some irregularity on SEB-Client connection attempt. + // Later we should handle this more accurately, and maybe indicate this to the monitoring board + // For example; check if there is already a connection for the userIdentifier and + // if true in which state it is. + log.debug("Unable to connect SEB-Client {} to exam {}", + clientAddress, + this.examSessionService.getRunningExam(examId).map(exam -> exam.name)); + throw new IllegalStateException("Unable to connect SEB-Client to exam"); + }); + + // Integrity checks: + if (!institutionId.equals(clientConnection.institutionId)) { + log.error("Instituion integrity violation with institution: {} on clientConnection: {}", + institutionId, + clientConnection); + throw new IllegalAccessError("Instituion integrity violation"); + } + + if (clientConnection.examId != null && !examId.equals(clientConnection.examId)) { + log.error("Exam integrity violation with examId: {} on clientConnection: {}", + examId, + clientConnection); + throw new IllegalAccessError("Exam integrity violation"); + } + + final ClientConnection updatedClientConnection = this.clientConnectionDAO.save(new ClientConnection( + clientConnection.id, + clientConnection.institutionId, + clientConnection.examId, + ClientConnection.ConnectionStatus.ESTABLISHED, + null, + userSessionId, + null, + null)).getOrThrow(); + + return updatedClientConnection; + }); + } + + @Override + public Result closeConnection(final String connectionToken) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void notifyPing(final Long connectionId, final long timestamp, final int pingNumber) { + final ClientConnectionDataInternal activeClientConnection = + this.examSessionCacheService.getActiveClientConnection(connectionId); + + if (activeClientConnection != null) { + activeClientConnection.pingMappings + .stream() + .forEach(pingIndicator -> pingIndicator.notifyPing(timestamp, pingNumber)); + } + } + + @Override + public void notifyClientEvent(final ClientEvent event, final Long connectionId) { + this.eventHandlingStrategy.accept(event); + + final ClientConnectionDataInternal activeClientConnection = + this.examSessionCacheService.getActiveClientConnection(connectionId); + + if (activeClientConnection != null) { + activeClientConnection.getindicatorMapping(event.eventType) + .stream() + .forEach(indicator -> indicator.notifyValueChange(event)); + } + } + + // TODO maybe we need a stronger connectionToken but for now a simple UUID is used + private String createToken() { + return UUID.randomUUID().toString(); + } + + private void examNotRunningException(final Long examId) { + log.error("The exam {} is not running", examId); + throw new IllegalStateException("The exam " + examId + " is not running"); + } + +}