diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java index 48c4a0b2..72600ef0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java @@ -25,6 +25,12 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCac /** Concrete EntityDAO interface of Exam entities */ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSupportDAO { + /** Use this to find an imported Exam with external id with like match + * + * @param internalQuizIdLike like match string that will be applied to like SQL match + * @return Result refer to Exam that matches the query. If more then one matches, error is reported.*/ + Result byExternalIdLike(String internalQuizIdLike); + /** Get a GrantEntity for the exam of specified id (PK) * This is actually a Exam instance but with no course data loaded. * @@ -230,4 +236,6 @@ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSup void markLMSAvailability(final String externalQuizId, final boolean available, final String updateId); void updateQuitPassword(Exam exam, String quitPassword); + + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/LmsSetupDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/LmsSetupDAO.java index c421f985..f7521c29 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/LmsSetupDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/LmsSetupDAO.java @@ -31,10 +31,16 @@ public interface LmsSetupDAO extends ActivatableEntityDAO, B Result getLmsAPIAccessProxyData(String lmsSetupId); /** Checks if the given LmsSetup instance is in sync with the version on - * data base by matching the update_time field + * database by matching the update_time field * - * @param lmsSetup LmsSetup instance to check if it is up to date + * @param lmsSetup LmsSetup instance to check if it is up-to-date * @return true if the update_time has the same value on persistent */ boolean isUpToDate(LmsSetup lmsSetup); + /** This wil find the internal LMSSetup with the universal connectionId if it exists on this server. + * + * @param connectionId The connectionId UUID (unique ID) + * @return the local LMSSetup DB PK ID */ + Result getLmsSetupIdByConnectionId(String connectionId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index 3efd43d1..de625ab8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -23,6 +23,7 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.mybatis.dynamic.sql.update.UpdateDSL; @@ -54,10 +55,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; @Lazy @Component @@ -93,6 +90,27 @@ public class ExamDAOImpl implements ExamDAO { .flatMap(this::toDomainModel); } + @Override + @Transactional(readOnly = true) + public Result byExternalIdLike(final String internalQuizIdLike) { + return Result.tryCatch(() -> { + final List execute = examRecordMapper.selectByExample() + .where(externalId, isLike(internalQuizIdLike)) + .build() + .execute(); + + if (execute == null || execute.isEmpty()) { + throw new NoResourceFoundException(EntityType.EXAM, "No exam found for external_id like" + internalQuizIdLike); + } + + if (execute.size() > 1) { + throw new IllegalStateException("To many exams found for external_id like" + internalQuizIdLike); + } + + return execute.get(0); + }).flatMap(this::toDomainModel); + } + @Override public Result examGrantEntityByPK(final Long id) { return this.examRecordDAO.recordById(id) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java index 189d8c51..b2d0b899 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java @@ -14,6 +14,7 @@ import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.mybatis.dynamic.sql.SqlBuilder; @@ -41,11 +42,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDyn import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.LmsSetupRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; @Lazy @Component @@ -75,6 +71,23 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { .flatMap(this::toDomainModel); } + @Override + @Transactional(readOnly = true) + public Result getLmsSetupIdByConnectionId(final String connectionId) { + return Result.tryCatch(() -> { + final List find = lmsSetupRecordMapper.selectIdsByExample() + .where(LmsSetupRecordDynamicSqlSupport.connectionId, isEqualTo(connectionId)) + .build() + .execute(); + + if (find == null || find.isEmpty()) { + throw new NoResourceFoundException(EntityType.LMS_SETUP, "No LMSSetup with connection_id:" + connectionId); + } + + return find.get(0); + }); + } + @Override @Transactional(readOnly = true) public Result> all(final Long institutionId, final Boolean active) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java index 53b7331d..cec0b99f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java @@ -35,6 +35,8 @@ public interface ExamAdminService { ProctoringAdminService getProctoringAdminService(); + Result applyPostCreationInitialization(Exam exam); + /** Get the exam domain object for the exam identifier (PK). * * @param examId the exam identifier @@ -165,178 +167,6 @@ public interface ExamAdminService { Result applyQuitPassword(Exam exam); - static void newExamFieldValidation(final POSTMapper postParams) { - noLMSFieldValidation(new Exam(postParams)); - } - - static Exam noLMSFieldValidation(final Exam exam) { - - // This only applies to exams that has no LMS - if (exam.lmsSetupId != null) { - return exam; - } - - final Collection validationErrors = new ArrayList<>(); - if (StringUtils.isBlank(exam.name)) { - validationErrors.add(APIMessage.fieldValidationError( - Domain.EXAM.ATTR_QUIZ_NAME, - "exam:quizName:notNull")); - } else { - final int length = exam.name.length(); - if (length < 3 || length > 255) { - validationErrors.add(APIMessage.fieldValidationError( - Domain.EXAM.ATTR_QUIZ_NAME, - "exam:quizName:size:3:255:" + length)); - } - } - - if (StringUtils.isBlank(exam.getStartURL())) { - validationErrors.add(APIMessage.fieldValidationError( - QuizData.QUIZ_ATTR_START_URL, - "exam:quiz_start_url:notNull")); - } else { - try { - new URL(exam.getStartURL()).toURI(); - } catch (final Exception e) { - validationErrors.add(APIMessage.fieldValidationError( - QuizData.QUIZ_ATTR_START_URL, - "exam:quiz_start_url:invalidURL")); - } - } - - if (exam.startTime == null) { - validationErrors.add(APIMessage.fieldValidationError( - Domain.EXAM.ATTR_QUIZ_START_TIME, - "exam:quizStartTime:notNull")); - } else if (exam.endTime != null) { - if (exam.startTime - .isAfter(exam.endTime)) { - validationErrors.add(APIMessage.fieldValidationError( - Domain.EXAM.ATTR_QUIZ_END_TIME, - "exam:quizEndTime:endBeforeStart")); - } - } - - if (!validationErrors.isEmpty()) { - throw new APIMessageException(validationErrors); - } - - return exam; - } - - /** Used to check threshold consistency for a given list of thresholds. - * Checks if all values are present (none null value) - * Checks if there are duplicates - *

- * If a check fails, the methods throws a APIMessageException with a FieldError to notify the caller - * - * @param thresholds List of Threshold */ - public static void checkThresholdConsistency(final List thresholds) { - if (thresholds != null) { - final List emptyThresholds = thresholds.stream() - .filter(t -> t.getValue() == null || t.getColor() == null) - .collect(Collectors.toList()); - - if (!emptyThresholds.isEmpty()) { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.EXAM.TYPE_NAME, - Domain.EXAM.ATTR_SUPPORTER, - "indicator:thresholds:thresholdEmpty"))); - } - - final Set values = thresholds.stream() - .map(t -> t.getValue()) - .collect(Collectors.toSet()); - - if (values.size() != thresholds.size()) { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.EXAM.TYPE_NAME, - Domain.EXAM.ATTR_SUPPORTER, - "indicator:thresholds:thresholdDuplicate"))); - } - } - } - - /** Used to check client group consistency for a given ClientGroup. - * Checks if correct entries for specific type - * - * If a check fails, the methods throws a APIMessageException with a FieldError to notify the caller - * - * @param clientGroup ClientGroup instance to check */ - public static T checkClientGroupConsistency(final T clientGroup) { - final ClientGroupType type = clientGroup.getType(); - if (type == null || type == ClientGroupType.NONE) { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.CLIENT_GROUP.TYPE_NAME, - Domain.CLIENT_GROUP.ATTR_TYPE, - "clientGroup:type:notNull"))); - } - - switch (type) { - case IP_V4_RANGE: { - checkIPRange(clientGroup.getIpRangeStart(), clientGroup.getIpRangeEnd()); - break; - } - case CLIENT_OS: { - checkClientOS(clientGroup.getClientOS()); - break; - } - default: { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.CLIENT_GROUP.TYPE_NAME, - Domain.CLIENT_GROUP.ATTR_TYPE, - "clientGroup:type:typeInvalid"))); - } - } - - return clientGroup; - } - - static void checkIPRange(final String ipRangeStart, final String ipRangeEnd) { - final long startIP = Utils.ipToLong(ipRangeStart); - if (StringUtils.isBlank(ipRangeStart) || startIP < 0) { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.CLIENT_GROUP.TYPE_NAME, - ClientGroup.ATTR_IP_RANGE_START, - "clientGroup:ipRangeStart:invalidIP"))); - } - final long endIP = Utils.ipToLong(ipRangeEnd); - if (StringUtils.isBlank(ipRangeEnd) || endIP < 0) { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.CLIENT_GROUP.TYPE_NAME, - ClientGroup.ATTR_IP_RANGE_END, - "clientGroup:ipRangeEnd:invalidIP"))); - } - - if (endIP <= startIP) { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.CLIENT_GROUP.TYPE_NAME, - ClientGroup.ATTR_IP_RANGE_START, - "clientGroup:ipRangeStart:invalidIPRange")), - APIMessage.fieldValidationError( - new FieldError( - Domain.CLIENT_GROUP.TYPE_NAME, - ClientGroup.ATTR_IP_RANGE_END, - "clientGroup:ipRangeEnd:invalidIPRange"))); - } - - } - - static void checkClientOS(final ClientOS clientOS) { - if (clientOS == null) { - throw new APIMessageException(APIMessage.fieldValidationError( - new FieldError( - Domain.CLIENT_GROUP.TYPE_NAME, - ClientGroupData.ATTR_CLIENT_OS, - "clientGroup:clientOS:notNull"))); - } - } + Result findExamByLmsIdentity(String courseId, String quizId, String identity); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamUtils.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamUtils.java new file mode 100644 index 00000000..90b1d3a9 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamUtils.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2019 ETH Zürich, IT Services + * + * 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.exam; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.POSTMapper; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.exam.*; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.FieldError; + +public final class ExamUtils { + + public static void newExamFieldValidation(final POSTMapper postParams) { + noLMSFieldValidation(new Exam(postParams)); + } + + public static Exam noLMSFieldValidation(final Exam exam) { + + // This only applies to exams that has no LMS + if (exam.lmsSetupId != null) { + return exam; + } + + final Collection validationErrors = new ArrayList<>(); + if (StringUtils.isBlank(exam.name)) { + validationErrors.add(APIMessage.fieldValidationError( + Domain.EXAM.ATTR_QUIZ_NAME, + "exam:quizName:notNull")); + } else { + final int length = exam.name.length(); + if (length < 3 || length > 255) { + validationErrors.add(APIMessage.fieldValidationError( + Domain.EXAM.ATTR_QUIZ_NAME, + "exam:quizName:size:3:255:" + length)); + } + } + + if (StringUtils.isBlank(exam.getStartURL())) { + validationErrors.add(APIMessage.fieldValidationError( + QuizData.QUIZ_ATTR_START_URL, + "exam:quiz_start_url:notNull")); + } else { + try { + new URL(exam.getStartURL()).toURI(); + } catch (final Exception e) { + validationErrors.add(APIMessage.fieldValidationError( + QuizData.QUIZ_ATTR_START_URL, + "exam:quiz_start_url:invalidURL")); + } + } + + if (exam.startTime == null) { + validationErrors.add(APIMessage.fieldValidationError( + Domain.EXAM.ATTR_QUIZ_START_TIME, + "exam:quizStartTime:notNull")); + } else if (exam.endTime != null) { + if (exam.startTime + .isAfter(exam.endTime)) { + validationErrors.add(APIMessage.fieldValidationError( + Domain.EXAM.ATTR_QUIZ_END_TIME, + "exam:quizEndTime:endBeforeStart")); + } + } + + if (!validationErrors.isEmpty()) { + throw new APIMessage.APIMessageException(validationErrors); + } + + return exam; + } + + /** Used to check threshold consistency for a given list of thresholds. + * Checks if all values are present (none null value) + * Checks if there are duplicates + *

+ * If a check fails, the methods throws a APIMessageException with a FieldError to notify the caller + * + * @param thresholds List of Threshold */ + public static void checkThresholdConsistency(final List thresholds) { + if (thresholds != null) { + final List emptyThresholds = thresholds.stream() + .filter(t -> t.getValue() == null || t.getColor() == null) + .toList(); + + if (!emptyThresholds.isEmpty()) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.EXAM.TYPE_NAME, + Domain.EXAM.ATTR_SUPPORTER, + "indicator:thresholds:thresholdEmpty"))); + } + + final Set values = thresholds.stream() + .map(Indicator.Threshold::getValue) + .collect(Collectors.toSet()); + + if (values.size() != thresholds.size()) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.EXAM.TYPE_NAME, + Domain.EXAM.ATTR_SUPPORTER, + "indicator:thresholds:thresholdDuplicate"))); + } + } + } + + /** Used to check client group consistency for a given ClientGroup. + * Checks if correct entries for specific type + *

+ * If a check fails, the methods throws a APIMessageException with a FieldError to notify the caller + * + * @param clientGroup ClientGroup instance to check */ + public static T checkClientGroupConsistency(final T clientGroup) { + final ClientGroupData.ClientGroupType type = clientGroup.getType(); + if (type == null || type == ClientGroupData.ClientGroupType.NONE) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + Domain.CLIENT_GROUP.ATTR_TYPE, + "clientGroup:type:notNull"))); + } + + switch (type) { + case IP_V4_RANGE: { + checkIPRange(clientGroup.getIpRangeStart(), clientGroup.getIpRangeEnd()); + break; + } + case CLIENT_OS: { + checkClientOS(clientGroup.getClientOS()); + break; + } + default: { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + Domain.CLIENT_GROUP.ATTR_TYPE, + "clientGroup:type:typeInvalid"))); + } + } + + return clientGroup; + } + + static void checkIPRange(final String ipRangeStart, final String ipRangeEnd) { + final long startIP = Utils.ipToLong(ipRangeStart); + if (StringUtils.isBlank(ipRangeStart) || startIP < 0) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_START, + "clientGroup:ipRangeStart:invalidIP"))); + } + final long endIP = Utils.ipToLong(ipRangeEnd); + if (StringUtils.isBlank(ipRangeEnd) || endIP < 0) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_END, + "clientGroup:ipRangeEnd:invalidIP"))); + } + + if (endIP <= startIP) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_START, + "clientGroup:ipRangeStart:invalidIPRange")), + APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_END, + "clientGroup:ipRangeEnd:invalidIPRange"))); + } + + } + + public static void checkClientOS(final ClientGroupData.ClientOS clientOS) { + if (clientOS == null) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroupData.ATTR_CLIENT_OS, + "clientGroup:clientOS:notNull"))); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index e63310e9..8f1ddc54 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -9,10 +9,14 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl; import java.security.MessageDigest; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.util.encoders.Hex; @@ -41,16 +45,13 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.Configuration import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils; import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringService; @Lazy @@ -71,6 +72,8 @@ public class ExamAdminServiceImpl implements ExamAdminService { private final ExamConfigurationValueService examConfigurationValueService; private final SEBRestrictionService sebRestrictionService; + private final ExamTemplateService examTemplateService; + protected ExamAdminServiceImpl( final ExamDAO examDAO, final ProctoringAdminService proctoringAdminService, @@ -80,6 +83,7 @@ public class ExamAdminServiceImpl implements ExamAdminService { final LmsAPIService lmsAPIService, final ExamConfigurationValueService examConfigurationValueService, final SEBRestrictionService sebRestrictionService, + final ExamTemplateService examTemplateService, final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled, final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.numerical.threshold:2}") int defaultNumericalTrustThreshold) { @@ -93,6 +97,7 @@ public class ExamAdminServiceImpl implements ExamAdminService { this.appSignatureKeyEnabled = appSignatureKeyEnabled; this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold; this.sebRestrictionService = sebRestrictionService; + this.examTemplateService = examTemplateService; } @Override @@ -100,6 +105,60 @@ public class ExamAdminServiceImpl implements ExamAdminService { return this.proctoringAdminService; } + @Override + public Result applyPostCreationInitialization(final Exam exam) { + final List errors = new ArrayList<>(); + + this.initAdditionalAttributes(exam) + .onErrorDo(error -> { + errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)); + return exam; + }) + .flatMap(this.examTemplateService::addDefinedIndicators) + .onErrorDo(error -> { + errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_INDICATOR.of(error)); + return exam; + }) + .flatMap(this.examTemplateService::addDefinedClientGroups) + .onErrorDo(error -> { + errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CLIENT_GROUPS.of(error)); + return exam; + }) + .flatMap(this.examTemplateService::initAdditionalTemplateAttributes) + .onErrorDo(error -> { + errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)); + return exam; + }) + .flatMap(this.examTemplateService::initExamConfiguration) + .onErrorDo(error -> { + if (error instanceof APIMessageException) { + errors.addAll(((APIMessageException) error).getAPIMessages()); + } else { + errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG.of(error)); + } + return exam; + }) + .flatMap(this::applyAdditionalSEBRestrictions) + .onErrorDo(error -> { + errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_RESTRICTION.of(error)); + return exam; + }); + + this.applyQuitPassword(exam); + + if (!errors.isEmpty()) { + errors.add(0, APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of( + exam.getModelId(), + API.PARAM_MODEL_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + exam.getModelId())); + + log.warn("Exam successfully created but some initialization did go wrong: {}", errors); + + throw new APIMessageException(errors); + } else { + return this.examDAO.byPK(exam.id); + } + } + @Override public Result examForPK(final Long examId) { return this.examDAO.byPK(examId); @@ -343,6 +402,35 @@ public class ExamAdminServiceImpl implements ExamAdminService { .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t)); } + @Override + public Result findExamByLmsIdentity( + final String courseId, + final String quizId, + final String identity) { + + for (final LmsType lmsType : LmsType.values()) { + switch (lmsType) { + case MOODLE_PLUGIN -> { + if (StringUtils.isBlank(quizId) || StringUtils.isBlank(courseId)) { + return Result.ofError(new APIMessageException( + APIMessage.ErrorMessage.FIELD_VALIDATION.of("Missing courseId or quizId"))); + } + + return examDAO.byExternalIdLike(MoodleUtils.getInternalQuizId( + quizId, + courseId, + Constants.PERCENTAGE_STRING, + Constants.PERCENTAGE_STRING)); + } + // TODO add other LMS types if they support full integration + } + } + + return Result.ofError( + new ResourceNotFoundException(EntityType.EXAM, + "Not found by LMS identity [" + courseId + "|"+ quizId+ "|"+ identity + "]")); + } + private Result initAdditionalAttributesForMoodleExams(final Exam exam) { return Result.tryCatch(() -> { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java index e82b0ab5..d35767b8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java @@ -12,9 +12,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result; public interface FullLmsIntegrationAPI { - Result createConnectionDetails(); - - Result updateConnectionDetails(); + Result applyConnectionDetails(FullLmsIntegrationService.IntegrationData data); Result deleteConnectionDetails(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java index d9976133..59814bec 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java @@ -14,11 +14,11 @@ import java.util.Map; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.util.Result; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; public interface FullLmsIntegrationService { - Result getLmsAPITemplate(String lmsUUID); - Result refreshAccessToken(String lmsUUID); Result applyFullLmsIntegration(Long lmsSetupId); @@ -45,4 +45,32 @@ public interface FullLmsIntegrationService { String courseId, String quizId, OutputStream out); + + @JsonIgnoreProperties(ignoreUnknown = true) + final class IntegrationData { + @JsonProperty("id") + public final String id; + @JsonProperty("name") + public final String name; + @JsonProperty("url") + public final String url; + @JsonProperty("access_token") + public final String access_token; + @JsonProperty("exam_templates") + public final Map exam_templates; + + public IntegrationData( + @JsonProperty("id") final String id, + @JsonProperty("name") final String name, + @JsonProperty("url") final String url, + @JsonProperty("access_token") final String access_token, + @JsonProperty("exam_templates") final Map exam_templates) { + + this.id = id; + this.name = name; + this.url = url; + this.access_token = access_token; + this.exam_templates = exam_templates; + } + } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/QuizLookupService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/QuizLookupService.java index d47c80af..aa8ae418 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/QuizLookupService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/QuizLookupService.java @@ -68,7 +68,6 @@ public interface QuizLookupService { * @param sortAttribute the sort attribute for the new Page * @param pageNumber the number of the Page to build * @param pageSize the size of the Page to build - * @param complete indicates if the quiz lookup that uses this page function has been completed yet * @return A Page of QuizData extracted from a given list of QuizData */ static Function> quizzesToPageFunction( final String sortAttribute, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java index cf978874..88dcc464 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java @@ -10,13 +10,16 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; import java.io.OutputStream; import java.util.Map; +import java.util.function.Function; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -25,9 +28,16 @@ import org.springframework.stereotype.Service; @Service @WebServiceProfile public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService { - @Override - public Result getLmsAPITemplate(final String lmsUUID) { - return Result.ofRuntimeError("TODO"); + + private final LmsSetupDAO lmsSetupDAO; + private final LmsAPIService lmsAPIService; + + public FullLmsIntegrationServiceImpl( + final LmsSetupDAO lmsSetupDAO, + final LmsAPIService lmsAPIService) { + + this.lmsSetupDAO = lmsSetupDAO; + this.lmsAPIService = lmsAPIService; } @Override @@ -58,9 +68,16 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService final String examTemplateId, final String quitPassword, final String quitLink) { - return Result.ofRuntimeError("TODO"); + + return lmsSetupDAO.getLmsSetupIdByConnectionId(lmsUUID) + .flatMap(lmsAPIService::getLmsAPITemplate) + .map(findQuizData(courseId, quizId)) + .map(createExam(examTemplateId, quitPassword, quitLink)); + } + + @Override public Result deleteExam( final String lmsUUID, @@ -78,8 +95,30 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService return Result.ofRuntimeError("TODO"); } - private Long findLMSSetup(final String lmsUUID) { - // TODO - return null; + private Function findQuizData( + final String courseId, + final String quizId) { + + return LmsAPITemplate -> { + // TODO find quiz data for quizId and courseId on LMS + return null; + }; } + + private Function createExam( + final String examTemplateId, + final String quitPassword, + final String quitLink) { + + return quizData -> { + // TODO create and store Exam with DAO and apply all post processing needed for import + return null; + }; + } + + private Exam createAdHocSupporterAccount(Exam exam) { + // TODO create an ad hoc supporter account for this exam and apply it to the exam + return exam; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java index 8f218ff8..ca071a5c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java @@ -462,7 +462,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate { } @Override - public Result createConnectionDetails() { + public Result applyConnectionDetails(final FullLmsIntegrationService.IntegrationData data) { if (this.lmsIntegrationAPI == null) { return Result.ofError( new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name())); @@ -472,31 +472,13 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate { log.debug("Create LMS connection details for LMSSetup: {}", lmsSetup()); } - return this.lmsAccessRequest.protectedRun(() -> this.lmsIntegrationAPI.createConnectionDetails() + return this.lmsAccessRequest.protectedRun(() -> this.lmsIntegrationAPI.applyConnectionDetails(data) .onError(error -> log.error( "Failed to run protected createConnectionDetails: {}", error.getMessage())) .getOrThrow()); } - @Override - public Result updateConnectionDetails() { - if (this.lmsIntegrationAPI == null) { - return Result.ofError( - new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name())); - } - - if (log.isDebugEnabled()) { - log.debug("Update LMS connection details for LMSSetup: {}", lmsSetup()); - } - - return this.lmsAccessRequest.protectedRun(() -> this.lmsIntegrationAPI.updateConnectionDetails() - .onError(error -> log.error( - "Failed to run protected updateConnectionDetails: {}", - error.getMessage())) - .getOrThrow()); - } - @Override public Result deleteConnectionDetails() { if (this.lmsIntegrationAPI == null) { @@ -514,4 +496,5 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate { error.getMessage())) .getOrThrow()); } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java index 43ac9372..18249422 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -421,12 +422,7 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms } @Override - public Result createConnectionDetails() { - return Result.ofRuntimeError("Not Supported"); - } - - @Override - public Result updateConnectionDetails() { + public Result applyConnectionDetails(final FullLmsIntegrationService.IntegrationData data) { return Result.ofRuntimeError("Not Supported"); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java index 4fb77805..dffb5e40 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java @@ -10,17 +10,13 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; public class MockupFullIntegration implements FullLmsIntegrationAPI { @Override - public Result createConnectionDetails() { - return Result.ofRuntimeError("TODO"); - } - - @Override - public Result updateConnectionDetails() { + public Result applyConnectionDetails(FullLmsIntegrationService.IntegrationData data) { return Result.ofRuntimeError("TODO"); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java index 09652f6c..63ff3399 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory; public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { @@ -27,12 +28,7 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { } @Override - public Result createConnectionDetails() { - return Result.ofRuntimeError("TODO"); - } - - @Override - public Result updateConnectionDetails() { + public Result applyConnectionDetails(FullLmsIntegrationService.IntegrationData data) { return Result.ofRuntimeError("TODO"); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 59bfcaeb..254c2f00 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.slf4j.Logger; @@ -408,12 +409,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm } @Override - public Result createConnectionDetails() { - return Result.ofRuntimeError("Not Supported"); - } - - @Override - public Result updateConnectionDetails() { + public Result applyConnectionDetails(final FullLmsIntegrationService.IntegrationData data) { return Result.ofRuntimeError("Not Supported"); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientGroupController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientGroupController.java index cf4343f7..f3525dbc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientGroupController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientGroupController.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamUtils; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -90,13 +91,13 @@ public class ClientGroupController extends EntityController validForCreate(final ClientGroup entity) { return super.validForCreate(entity) - .map(ExamAdminService::checkClientGroupConsistency); + .map(ExamUtils::checkClientGroupConsistency); } @Override protected Result validForSave(final ClientGroup entity) { return super.validForSave(entity) - .map(ExamAdminService::checkClientGroupConsistency); + .map(ExamUtils::checkClientGroupConsistency); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index ef6f6964..fb98b59d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -15,6 +15,7 @@ import java.util.stream.Collectors; import javax.validation.Valid; import ch.ethz.seb.sebserver.gbl.util.Cryptor; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamUtils; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; @@ -34,7 +35,6 @@ import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; -import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.model.Domain; @@ -67,7 +67,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; -import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService; import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; @@ -88,7 +87,6 @@ public class ExamAdministrationController extends EntityController { private final UserDAO userDAO; private final ExamAdminService examAdminService; private final RemoteProctoringRoomService remoteProctoringRoomService; - private final ExamTemplateService examTemplateService; private final LmsAPIService lmsAPIService; private final ExamSessionService examSessionService; private final SEBRestrictionService sebRestrictionService; @@ -106,7 +104,6 @@ public class ExamAdministrationController extends EntityController { final UserDAO userDAO, final ExamAdminService examAdminService, final RemoteProctoringRoomService remoteProctoringRoomService, - final ExamTemplateService examTemplateService, final ExamSessionService examSessionService, final SEBRestrictionService sebRestrictionService, final SecurityKeyService securityKeyService, @@ -123,7 +120,6 @@ public class ExamAdministrationController extends EntityController { this.userDAO = userDAO; this.examAdminService = examAdminService; this.remoteProctoringRoomService = remoteProctoringRoomService; - this.examTemplateService = examTemplateService; this.lmsAPIService = lmsAPIService; this.examSessionService = examSessionService; this.sebRestrictionService = sebRestrictionService; @@ -596,7 +592,7 @@ public class ExamAdministrationController extends EntityController { // NO LMS based exam is possible since v1.6 if (quizId == null) { - ExamAdminService.newExamFieldValidation(postParams); + ExamUtils.newExamFieldValidation(postParams); return new Exam(postParams); } else { return this.lmsAPIService @@ -613,57 +609,7 @@ public class ExamAdministrationController extends EntityController { @Override protected Result notifyCreated(final Exam entity) { - final List errors = new ArrayList<>(); - - this.examAdminService - .initAdditionalAttributes(entity) - .onErrorDo(error -> { - errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)); - return entity; - }) - .flatMap(this.examTemplateService::addDefinedIndicators) - .onErrorDo(error -> { - errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_INDICATOR.of(error)); - return entity; - }) - .flatMap(this.examTemplateService::addDefinedClientGroups) - .onErrorDo(error -> { - errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CLIENT_GROUPS.of(error)); - return entity; - }) - .flatMap(this.examTemplateService::initAdditionalTemplateAttributes) - .onErrorDo(error -> { - errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)); - return entity; - }) - .flatMap(this.examTemplateService::initExamConfiguration) - .onErrorDo(error -> { - if (error instanceof APIMessageException) { - errors.addAll(((APIMessageException) error).getAPIMessages()); - } else { - errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG.of(error)); - } - return entity; - }) - .flatMap(this.examAdminService::applyAdditionalSEBRestrictions) - .onErrorDo(error -> { - errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_RESTRICTION.of(error)); - return entity; - }); - - this.examAdminService.applyQuitPassword(entity); - - if (!errors.isEmpty()) { - errors.add(0, ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of( - entity.getModelId(), - API.PARAM_MODEL_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + entity.getModelId())); - - log.warn("Exam successfully created but some initialization did go wrong: {}", errors); - - throw new APIMessageException(errors); - } else { - return this.examDAO.byPK(entity.id); - } + return examAdminService.applyPostCreationInitialization(entity); } @Override @@ -683,7 +629,7 @@ public class ExamAdministrationController extends EntityController { protected Result validForSave(final Exam entity) { return super.validForSave(entity) .map(this::checkExamSupporterRole) - .map(ExamAdminService::noLMSFieldValidation) + .map(ExamUtils::noLMSFieldValidation) .map(this::checkQuitPasswordChange); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java index c3e59569..f65666a0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java @@ -15,6 +15,7 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamUtils; import org.apache.commons.lang3.StringUtils; import org.mybatis.dynamic.sql.SqlTable; import org.slf4j.Logger; @@ -381,7 +382,7 @@ public class ExamTemplateController extends EntityController protected Result validForCreate(final Indicator entity) { return super.validForCreate(entity) .map(indicator -> { - ExamAdminService.checkThresholdConsistency(indicator.thresholds); + ExamUtils.checkThresholdConsistency(indicator.thresholds); return indicator; }); } @@ -99,7 +100,7 @@ public class IndicatorController extends EntityController protected Result validForSave(final Indicator entity) { return super.validForSave(entity) .map(indicator -> { - ExamAdminService.checkThresholdConsistency(indicator.thresholds); + ExamUtils.checkThresholdConsistency(indicator.thresholds); return indicator; }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizController.java index 4522cc20..50280bf6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizController.java @@ -60,8 +60,8 @@ public class QuizController { /** This is called by Spring to initialize the WebDataBinder and is used here to * initialize the default value binding for the institutionId request-parameter - * that has the current users insitutionId as default. - * + * that has the current users institutionId as default. + *

* See also UserService.addUsersInstitutionDefaultPropertySupport */ @InitBinder public void initBinder(final WebDataBinder binder) {