SEBSERV-417 impl still in progress

This commit is contained in:
anhefti 2024-04-15 08:46:55 +02:00
parent a81447601b
commit 5cc7624f73
21 changed files with 458 additions and 313 deletions

View file

@ -25,6 +25,12 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCac
/** Concrete EntityDAO interface of Exam entities */ /** Concrete EntityDAO interface of Exam entities */
public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSupportDAO<Exam> { public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSupportDAO<Exam> {
/** 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<Exam> byExternalIdLike(String internalQuizIdLike);
/** Get a GrantEntity for the exam of specified id (PK) /** Get a GrantEntity for the exam of specified id (PK)
* This is actually a Exam instance but with no course data loaded. * This is actually a Exam instance but with no course data loaded.
* *
@ -230,4 +236,6 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
void markLMSAvailability(final String externalQuizId, final boolean available, final String updateId); void markLMSAvailability(final String externalQuizId, final boolean available, final String updateId);
void updateQuitPassword(Exam exam, String quitPassword); void updateQuitPassword(Exam exam, String quitPassword);
} }

View file

@ -31,10 +31,16 @@ public interface LmsSetupDAO extends ActivatableEntityDAO<LmsSetup, LmsSetup>, B
Result<ProxyData> getLmsAPIAccessProxyData(String lmsSetupId); Result<ProxyData> getLmsAPIAccessProxyData(String lmsSetupId);
/** Checks if the given LmsSetup instance is in sync with the version on /** 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 */ * @return true if the update_time has the same value on persistent */
boolean isUpToDate(LmsSetup lmsSetup); 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<Long> getLmsSetupIdByConnectionId(String connectionId);
} }

View file

@ -23,6 +23,7 @@ import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.update.UpdateDSL; 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.AdditionalAttributeRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord; 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.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 @Lazy
@Component @Component
@ -93,6 +90,27 @@ public class ExamDAOImpl implements ExamDAO {
.flatMap(this::toDomainModel); .flatMap(this::toDomainModel);
} }
@Override
@Transactional(readOnly = true)
public Result<Exam> byExternalIdLike(final String internalQuizIdLike) {
return Result.tryCatch(() -> {
final List<ExamRecord> 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 @Override
public Result<GrantEntity> examGrantEntityByPK(final Long id) { public Result<GrantEntity> examGrantEntityByPK(final Long id) {
return this.examRecordDAO.recordById(id) return this.examRecordDAO.recordById(id)

View file

@ -14,6 +14,7 @@ import java.util.*;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlBuilder; 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.mapper.LmsSetupRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.LmsSetupRecord; 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.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 @Lazy
@Component @Component
@ -75,6 +71,23 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
.flatMap(this::toDomainModel); .flatMap(this::toDomainModel);
} }
@Override
@Transactional(readOnly = true)
public Result<Long> getLmsSetupIdByConnectionId(final String connectionId) {
return Result.tryCatch(() -> {
final List<Long> 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 @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<Collection<LmsSetup>> all(final Long institutionId, final Boolean active) { public Result<Collection<LmsSetup>> all(final Long institutionId, final Boolean active) {

View file

@ -35,6 +35,8 @@ public interface ExamAdminService {
ProctoringAdminService getProctoringAdminService(); ProctoringAdminService getProctoringAdminService();
Result<Exam> applyPostCreationInitialization(Exam exam);
/** Get the exam domain object for the exam identifier (PK). /** Get the exam domain object for the exam identifier (PK).
* *
* @param examId the exam identifier * @param examId the exam identifier
@ -165,178 +167,6 @@ public interface ExamAdminService {
Result<Exam> applyQuitPassword(Exam exam); Result<Exam> applyQuitPassword(Exam exam);
static void newExamFieldValidation(final POSTMapper postParams) { Result<Exam> findExamByLmsIdentity(String courseId, String quizId, String identity);
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<APIMessage> 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
* <p>
* 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<Threshold> thresholds) {
if (thresholds != null) {
final List<Threshold> 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<Double> 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 extends ClientGroupData> 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")));
}
}
} }

View file

@ -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<APIMessage> 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
* <p>
* 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<Indicator.Threshold> thresholds) {
if (thresholds != null) {
final List<Indicator.Threshold> 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<Double> 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
* <p>
* 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 extends ClientGroupData> 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")));
}
}
}

View file

@ -9,10 +9,14 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; 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.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.encoders.Hex; 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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils; 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.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; 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.exam.ProctoringAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; 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.LmsAPITemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; 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; import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringService;
@Lazy @Lazy
@ -71,6 +72,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
private final ExamConfigurationValueService examConfigurationValueService; private final ExamConfigurationValueService examConfigurationValueService;
private final SEBRestrictionService sebRestrictionService; private final SEBRestrictionService sebRestrictionService;
private final ExamTemplateService examTemplateService;
protected ExamAdminServiceImpl( protected ExamAdminServiceImpl(
final ExamDAO examDAO, final ExamDAO examDAO,
final ProctoringAdminService proctoringAdminService, final ProctoringAdminService proctoringAdminService,
@ -80,6 +83,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
final LmsAPIService lmsAPIService, final LmsAPIService lmsAPIService,
final ExamConfigurationValueService examConfigurationValueService, final ExamConfigurationValueService examConfigurationValueService,
final SEBRestrictionService sebRestrictionService, 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.enabled:false}") boolean appSignatureKeyEnabled,
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.numerical.threshold:2}") int defaultNumericalTrustThreshold) { 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.appSignatureKeyEnabled = appSignatureKeyEnabled;
this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold; this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold;
this.sebRestrictionService = sebRestrictionService; this.sebRestrictionService = sebRestrictionService;
this.examTemplateService = examTemplateService;
} }
@Override @Override
@ -100,6 +105,60 @@ public class ExamAdminServiceImpl implements ExamAdminService {
return this.proctoringAdminService; return this.proctoringAdminService;
} }
@Override
public Result<Exam> applyPostCreationInitialization(final Exam exam) {
final List<APIMessage> 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 @Override
public Result<Exam> examForPK(final Long examId) { public Result<Exam> examForPK(final Long examId) {
return this.examDAO.byPK(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)); .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t));
} }
@Override
public Result<Exam> 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<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) { private Result<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {

View file

@ -12,9 +12,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
public interface FullLmsIntegrationAPI { public interface FullLmsIntegrationAPI {
Result<Void> createConnectionDetails(); Result<Void> applyConnectionDetails(FullLmsIntegrationService.IntegrationData data);
Result<Void> updateConnectionDetails();
Result<Void> deleteConnectionDetails(); Result<Void> deleteConnectionDetails();

View file

@ -14,11 +14,11 @@ import java.util.Map;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
public interface FullLmsIntegrationService { public interface FullLmsIntegrationService {
Result<LmsAPITemplate> getLmsAPITemplate(String lmsUUID);
Result<Void> refreshAccessToken(String lmsUUID); Result<Void> refreshAccessToken(String lmsUUID);
Result<Void> applyFullLmsIntegration(Long lmsSetupId); Result<Void> applyFullLmsIntegration(Long lmsSetupId);
@ -45,4 +45,32 @@ public interface FullLmsIntegrationService {
String courseId, String courseId,
String quizId, String quizId,
OutputStream out); 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<String, String> 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<String, String> exam_templates) {
this.id = id;
this.name = name;
this.url = url;
this.access_token = access_token;
this.exam_templates = exam_templates;
}
}
} }

View file

@ -68,7 +68,6 @@ public interface QuizLookupService {
* @param sortAttribute the sort attribute for the new Page * @param sortAttribute the sort attribute for the new Page
* @param pageNumber the number of the Page to build * @param pageNumber the number of the Page to build
* @param pageSize the size 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 */ * @return A Page of QuizData extracted from a given list of QuizData */
static Function<LookupResult, Page<QuizData>> quizzesToPageFunction( static Function<LookupResult, Page<QuizData>> quizzesToPageFunction(
final String sortAttribute, final String sortAttribute,

View file

@ -10,13 +10,16 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; 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.FullLmsIntegrationService;
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.LmsAPITemplate;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -25,9 +28,16 @@ import org.springframework.stereotype.Service;
@Service @Service
@WebServiceProfile @WebServiceProfile
public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService { public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService {
@Override
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsUUID) { private final LmsSetupDAO lmsSetupDAO;
return Result.ofRuntimeError("TODO"); private final LmsAPIService lmsAPIService;
public FullLmsIntegrationServiceImpl(
final LmsSetupDAO lmsSetupDAO,
final LmsAPIService lmsAPIService) {
this.lmsSetupDAO = lmsSetupDAO;
this.lmsAPIService = lmsAPIService;
} }
@Override @Override
@ -58,9 +68,16 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
final String examTemplateId, final String examTemplateId,
final String quitPassword, final String quitPassword,
final String quitLink) { final String quitLink) {
return Result.ofRuntimeError("TODO");
return lmsSetupDAO.getLmsSetupIdByConnectionId(lmsUUID)
.flatMap(lmsAPIService::getLmsAPITemplate)
.map(findQuizData(courseId, quizId))
.map(createExam(examTemplateId, quitPassword, quitLink));
} }
@Override @Override
public Result<EntityKey> deleteExam( public Result<EntityKey> deleteExam(
final String lmsUUID, final String lmsUUID,
@ -78,8 +95,30 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
return Result.ofRuntimeError("TODO"); return Result.ofRuntimeError("TODO");
} }
private Long findLMSSetup(final String lmsUUID) { private Function<LmsAPITemplate, QuizData> findQuizData(
// TODO final String courseId,
return null; final String quizId) {
return LmsAPITemplate -> {
// TODO find quiz data for quizId and courseId on LMS
return null;
};
} }
private Function<QuizData, Exam> 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;
}
} }

View file

@ -462,7 +462,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
} }
@Override @Override
public Result<Void> createConnectionDetails() { public Result<Void> applyConnectionDetails(final FullLmsIntegrationService.IntegrationData data) {
if (this.lmsIntegrationAPI == null) { if (this.lmsIntegrationAPI == null) {
return Result.ofError( return Result.ofError(
new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name())); 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()); 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( .onError(error -> log.error(
"Failed to run protected createConnectionDetails: {}", "Failed to run protected createConnectionDetails: {}",
error.getMessage())) error.getMessage()))
.getOrThrow()); .getOrThrow());
} }
@Override
public Result<Void> 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 @Override
public Result<Void> deleteConnectionDetails() { public Result<Void> deleteConnectionDetails() {
if (this.lmsIntegrationAPI == null) { if (this.lmsIntegrationAPI == null) {
@ -514,4 +496,5 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
error.getMessage())) error.getMessage()))
.getOrThrow()); .getOrThrow());
} }
} }

View file

@ -22,6 +22,7 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@ -421,12 +422,7 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
} }
@Override @Override
public Result<Void> createConnectionDetails() { public Result<Void> applyConnectionDetails(final FullLmsIntegrationService.IntegrationData data) {
return Result.ofRuntimeError("Not Supported");
}
@Override
public Result<Void> updateConnectionDetails() {
return Result.ofRuntimeError("Not Supported"); return Result.ofRuntimeError("Not Supported");
} }

View file

@ -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.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
public class MockupFullIntegration implements FullLmsIntegrationAPI { public class MockupFullIntegration implements FullLmsIntegrationAPI {
@Override @Override
public Result<Void> createConnectionDetails() { public Result<Void> applyConnectionDetails(FullLmsIntegrationService.IntegrationData data) {
return Result.ofRuntimeError("TODO");
}
@Override
public Result<Void> updateConnectionDetails() {
return Result.ofRuntimeError("TODO"); return Result.ofRuntimeError("TODO");
} }

View file

@ -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.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.util.Result; 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.FullLmsIntegrationAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
@ -27,12 +28,7 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
} }
@Override @Override
public Result<Void> createConnectionDetails() { public Result<Void> applyConnectionDetails(FullLmsIntegrationService.IntegrationData data) {
return Result.ofRuntimeError("TODO");
}
@Override
public Result<Void> updateConnectionDetails() {
return Result.ofRuntimeError("TODO"); return Result.ofRuntimeError("TODO");
} }

View file

@ -19,6 +19,7 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -408,12 +409,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
} }
@Override @Override
public Result<Void> createConnectionDetails() { public Result<Void> applyConnectionDetails(final FullLmsIntegrationService.IntegrationData data) {
return Result.ofRuntimeError("Not Supported");
}
@Override
public Result<Void> updateConnectionDetails() {
return Result.ofRuntimeError("Not Supported"); return Result.ofRuntimeError("Not Supported");
} }

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; 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.mybatis.dynamic.sql.SqlTable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -90,13 +91,13 @@ public class ClientGroupController extends EntityController<ClientGroup, ClientG
@Override @Override
protected Result<ClientGroup> validForCreate(final ClientGroup entity) { protected Result<ClientGroup> validForCreate(final ClientGroup entity) {
return super.validForCreate(entity) return super.validForCreate(entity)
.map(ExamAdminService::checkClientGroupConsistency); .map(ExamUtils::checkClientGroupConsistency);
} }
@Override @Override
protected Result<ClientGroup> validForSave(final ClientGroup entity) { protected Result<ClientGroup> validForSave(final ClientGroup entity) {
return super.validForSave(entity) return super.validForSave(entity)
.map(ExamAdminService::checkClientGroupConsistency); .map(ExamUtils::checkClientGroupConsistency);
} }
@Override @Override

View file

@ -15,6 +15,7 @@ import java.util.stream.Collectors;
import javax.validation.Valid; import javax.validation.Valid;
import ch.ethz.seb.sebserver.gbl.util.Cryptor; 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 ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; 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.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; 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.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.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain; 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.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; 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.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.institution.SecurityKeyService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
@ -88,7 +87,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
private final UserDAO userDAO; private final UserDAO userDAO;
private final ExamAdminService examAdminService; private final ExamAdminService examAdminService;
private final RemoteProctoringRoomService remoteProctoringRoomService; private final RemoteProctoringRoomService remoteProctoringRoomService;
private final ExamTemplateService examTemplateService;
private final LmsAPIService lmsAPIService; private final LmsAPIService lmsAPIService;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final SEBRestrictionService sebRestrictionService; private final SEBRestrictionService sebRestrictionService;
@ -106,7 +104,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
final UserDAO userDAO, final UserDAO userDAO,
final ExamAdminService examAdminService, final ExamAdminService examAdminService,
final RemoteProctoringRoomService remoteProctoringRoomService, final RemoteProctoringRoomService remoteProctoringRoomService,
final ExamTemplateService examTemplateService,
final ExamSessionService examSessionService, final ExamSessionService examSessionService,
final SEBRestrictionService sebRestrictionService, final SEBRestrictionService sebRestrictionService,
final SecurityKeyService securityKeyService, final SecurityKeyService securityKeyService,
@ -123,7 +120,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
this.userDAO = userDAO; this.userDAO = userDAO;
this.examAdminService = examAdminService; this.examAdminService = examAdminService;
this.remoteProctoringRoomService = remoteProctoringRoomService; this.remoteProctoringRoomService = remoteProctoringRoomService;
this.examTemplateService = examTemplateService;
this.lmsAPIService = lmsAPIService; this.lmsAPIService = lmsAPIService;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.sebRestrictionService = sebRestrictionService; this.sebRestrictionService = sebRestrictionService;
@ -596,7 +592,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
// NO LMS based exam is possible since v1.6 // NO LMS based exam is possible since v1.6
if (quizId == null) { if (quizId == null) {
ExamAdminService.newExamFieldValidation(postParams); ExamUtils.newExamFieldValidation(postParams);
return new Exam(postParams); return new Exam(postParams);
} else { } else {
return this.lmsAPIService return this.lmsAPIService
@ -613,57 +609,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
@Override @Override
protected Result<Exam> notifyCreated(final Exam entity) { protected Result<Exam> notifyCreated(final Exam entity) {
final List<APIMessage> errors = new ArrayList<>(); return examAdminService.applyPostCreationInitialization(entity);
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);
}
} }
@Override @Override
@ -683,7 +629,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
protected Result<Exam> validForSave(final Exam entity) { protected Result<Exam> validForSave(final Exam entity) {
return super.validForSave(entity) return super.validForSave(entity)
.map(this::checkExamSupporterRole) .map(this::checkExamSupporterRole)
.map(ExamAdminService::noLMSFieldValidation) .map(ExamUtils::noLMSFieldValidation)
.map(this::checkQuitPasswordChange); .map(this::checkQuitPasswordChange);
} }

View file

@ -15,6 +15,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; import javax.validation.Valid;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; 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.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -381,7 +382,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
null, null,
postMap.getLong(ClientGroupTemplate.ATTR_EXAM_TEMPLATE_ID), postMap.getLong(ClientGroupTemplate.ATTR_EXAM_TEMPLATE_ID),
postMap)) postMap))
.map(ExamAdminService::checkClientGroupConsistency) .map(ExamUtils::checkClientGroupConsistency)
.flatMap(this.examTemplateDAO::createNewClientGroupTemplate) .flatMap(this.examTemplateDAO::createNewClientGroupTemplate)
.flatMap(this.userActivityLogDAO::logCreate) .flatMap(this.userActivityLogDAO::logCreate)
.getOrThrow(); .getOrThrow();
@ -403,7 +404,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
this.checkModifyPrivilege(institutionId); this.checkModifyPrivilege(institutionId);
return this.beanValidationService return this.beanValidationService
.validateBean(modifyData) .validateBean(modifyData)
.map(ExamAdminService::checkClientGroupConsistency) .map(ExamUtils::checkClientGroupConsistency)
.flatMap(this.examTemplateDAO::saveClientGroupTemplate) .flatMap(this.examTemplateDAO::saveClientGroupTemplate)
.flatMap(this.userActivityLogDAO::logModify) .flatMap(this.userActivityLogDAO::logModify)
.getOrThrow(); .getOrThrow();
@ -547,7 +548,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
} }
private IndicatorTemplate checkIndicatorConsistency(final IndicatorTemplate indicatorTemplate) { private IndicatorTemplate checkIndicatorConsistency(final IndicatorTemplate indicatorTemplate) {
ExamAdminService.checkThresholdConsistency(indicatorTemplate.thresholds); ExamUtils.checkThresholdConsistency(indicatorTemplate.thresholds);
return indicatorTemplate; return indicatorTemplate;
} }

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; 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.mybatis.dynamic.sql.SqlTable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -90,7 +91,7 @@ public class IndicatorController extends EntityController<Indicator, Indicator>
protected Result<Indicator> validForCreate(final Indicator entity) { protected Result<Indicator> validForCreate(final Indicator entity) {
return super.validForCreate(entity) return super.validForCreate(entity)
.map(indicator -> { .map(indicator -> {
ExamAdminService.checkThresholdConsistency(indicator.thresholds); ExamUtils.checkThresholdConsistency(indicator.thresholds);
return indicator; return indicator;
}); });
} }
@ -99,7 +100,7 @@ public class IndicatorController extends EntityController<Indicator, Indicator>
protected Result<Indicator> validForSave(final Indicator entity) { protected Result<Indicator> validForSave(final Indicator entity) {
return super.validForSave(entity) return super.validForSave(entity)
.map(indicator -> { .map(indicator -> {
ExamAdminService.checkThresholdConsistency(indicator.thresholds); ExamUtils.checkThresholdConsistency(indicator.thresholds);
return indicator; return indicator;
}); });
} }

View file

@ -60,8 +60,8 @@ public class QuizController {
/** This is called by Spring to initialize the WebDataBinder and is used here to /** This is called by Spring to initialize the WebDataBinder and is used here to
* initialize the default value binding for the institutionId request-parameter * 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.
* * <p>
* See also UserService.addUsersInstitutionDefaultPropertySupport */ * See also UserService.addUsersInstitutionDefaultPropertySupport */
@InitBinder @InitBinder
public void initBinder(final WebDataBinder binder) { public void initBinder(final WebDataBinder binder) {