SEBSERV-62 service implementations and preparing for config download
This commit is contained in:
parent
8d9cf58e69
commit
44688429bb
21 changed files with 644 additions and 181 deletions
|
@ -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<ClientConnection, ClientConnection> {
|
||||
|
||||
Result<ClientConnection> byConnectionToken(Long institutionId, String connectionToken);
|
||||
|
||||
}
|
||||
|
|
|
@ -183,6 +183,37 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<ClientConnection> byConnectionToken(
|
||||
final Long institutionId,
|
||||
final String connectionToken) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
final List<ClientConnectionRecord> 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<ClientConnectionRecord> recordById(final Long id) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Long, ConfigurationAttribute> attributes = this.configurationAttributeDAO.getAllRootAttributes()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
ConfigurationAttribute::getId,
|
||||
Function.identity()));
|
||||
|
||||
final List<ConfigurationAttribute> 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<Long, ConfigurationValue> 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
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<ConfigurationValueValidator> validators;
|
||||
private final Map<String, XMLValueConverter> convertersByAttributeName;
|
||||
private final Map<AttributeType, XMLValueConverter> convertersByAttributeType;
|
||||
|
||||
protected SebExamConfigServiceImpl(
|
||||
final ExamConfigIO examConfigIO,
|
||||
final ConfigurationAttributeDAO configurationAttributeDAO,
|
||||
final ConfigurationValueDAO configurationValueDAO,
|
||||
final ConfigurationDAO configurationDAO,
|
||||
final Collection<ConfigurationValueValidator> validators,
|
||||
final Collection<XMLValueConverter> converters) {
|
||||
final Collection<ConfigurationValueValidator> 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<Long, ConfigurationAttribute> attributes = this.configurationAttributeDAO.getAllRootAttributes()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
ConfigurationAttribute::getId,
|
||||
Function.identity()));
|
||||
|
||||
final List<ConfigurationAttribute> 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<Long, ConfigurationValue> 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
|
||||
|
|
|
@ -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<String, XMLValueConverter> convertersByAttributeName;
|
||||
private final Map<AttributeType, XMLValueConverter> convertersByAttributeType;
|
||||
|
||||
public XMLValueConverterServiceImpl(final Collection<XMLValueConverter> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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<AttributeType> 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();
|
||||
|
|
|
@ -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<Exam> 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<Collection<Exam>> getRunningExamsForInstitution(Long institutionId);
|
||||
|
||||
void notifyPing(Long connectionId, long timestamp, int pingNumber);
|
||||
|
||||
void notifyClientEvent(final ClientEvent event, Long connectionId);
|
||||
|
||||
}
|
||||
|
|
|
@ -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<ClientConnection> createClientConnection(Long instituionId, Long examId);
|
||||
Result<ClientConnection> createClientConnection(
|
||||
Long institutionId,
|
||||
String clientAddress,
|
||||
Long examId);
|
||||
|
||||
Result<ClientConnection> establishClientConnection(
|
||||
final Long institutionId,
|
||||
String connectionToken,
|
||||
Long examId,
|
||||
final String clientAddress,
|
||||
String userSessionId);
|
||||
|
||||
Result<ClientConnection> closeConnection(String connectionToken);
|
||||
|
||||
void notifyPing(Long connectionId, long timestamp, int pingNumber);
|
||||
|
||||
void notifyClientEvent(final ClientEvent event, Long connectionId);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ClientConnection> 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<ClientConnection> 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<ClientConnection> 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");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue