SEBSERV-417 create delete exam from Moodle
This commit is contained in:
parent
c0919ce0cf
commit
ff89864b19
14 changed files with 247 additions and 82 deletions
|
@ -18,6 +18,8 @@ import java.util.Map;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
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.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -415,6 +417,31 @@ public final class Exam implements GrantEntity {
|
||||||
return this.additionalAttributes.get(attrName);
|
return this.additionalAttributes.get(attrName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Exam printSecureCopy() {
|
||||||
|
return new Exam(
|
||||||
|
id,
|
||||||
|
institutionId,
|
||||||
|
lmsSetupId,
|
||||||
|
externalId,
|
||||||
|
lmsAvailable,
|
||||||
|
name,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
type,
|
||||||
|
owner,
|
||||||
|
supporter,
|
||||||
|
status,
|
||||||
|
"--",
|
||||||
|
sebRestriction,
|
||||||
|
"--",
|
||||||
|
active,
|
||||||
|
lastUpdate,
|
||||||
|
examTemplateId,
|
||||||
|
lastModified,
|
||||||
|
Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
|
|
@ -33,7 +33,8 @@ public final class LmsSetupTestResult {
|
||||||
TOKEN_REQUEST,
|
TOKEN_REQUEST,
|
||||||
QUIZ_ACCESS_API_REQUEST,
|
QUIZ_ACCESS_API_REQUEST,
|
||||||
QUIZ_RESTRICTION_API_REQUEST,
|
QUIZ_RESTRICTION_API_REQUEST,
|
||||||
TEMPLATE_CREATION
|
TEMPLATE_CREATION,
|
||||||
|
APPLY_FULL_INTEGRATION,
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonProperty(Domain.LMS_SETUP.ATTR_LMS_TYPE)
|
@JsonProperty(Domain.LMS_SETUP.ATTR_LMS_TYPE)
|
||||||
|
@ -138,6 +139,10 @@ public final class LmsSetupTestResult {
|
||||||
return new LmsSetupTestResult(lmsType, new Error(ErrorType.QUIZ_RESTRICTION_API_REQUEST, message));
|
return new LmsSetupTestResult(lmsType, new Error(ErrorType.QUIZ_RESTRICTION_API_REQUEST, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LmsSetupTestResult ofFullIntegrationAPIError(final LmsSetup.LmsType lmsType, final String message) {
|
||||||
|
return new LmsSetupTestResult(lmsType, new Error(ErrorType.APPLY_FULL_INTEGRATION, message));
|
||||||
|
}
|
||||||
|
|
||||||
public final static class Error {
|
public final static class Error {
|
||||||
|
|
||||||
@JsonProperty(ATTR_ERROR_TYPE)
|
@JsonProperty(ATTR_ERROR_TYPE)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.util.stream.Stream;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.*;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.*;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
||||||
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.eclipse.swt.layout.GridData;
|
import org.eclipse.swt.layout.GridData;
|
||||||
|
@ -204,7 +205,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
final boolean editable = modifyGrant && (exam.getStatus() == ExamStatus.UP_COMING || exam.getStatus() == ExamStatus.RUNNING);
|
final boolean editable = modifyGrant && (exam.getStatus() == ExamStatus.UP_COMING || exam.getStatus() == ExamStatus.RUNNING);
|
||||||
final boolean signatureKeyCheckEnabled = BooleanUtils.toBoolean(
|
final boolean signatureKeyCheckEnabled = BooleanUtils.toBoolean(
|
||||||
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||||
final boolean sebRestrictionAvailable = readonly && testSEBRestrictionAPI(exam);
|
final boolean sebRestrictionAvailable = readonly && hasSEBRestrictionAPI(exam);
|
||||||
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
||||||
.getBuilder(CheckSEBRestriction.class)
|
.getBuilder(CheckSEBRestriction.class)
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
|
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
|
||||||
|
@ -742,25 +743,22 @@ public class ExamForm implements TemplateComposer {
|
||||||
throw new RuntimeException("Error while handle exam import setup failure:", e);
|
throw new RuntimeException("Error while handle exam import setup failure:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean testSEBRestrictionAPI(final Exam exam) {
|
private boolean hasSEBRestrictionAPI(final Exam exam) {
|
||||||
if (exam.lmsSetupId == null || !exam.isLmsAvailable() || exam.status == ExamStatus.ARCHIVED) {
|
if (exam.lmsSetupId == null || !exam.isLmsAvailable() || exam.status == ExamStatus.ARCHIVED) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the testing endpoint with the specified data to test
|
// get LMSSetup
|
||||||
final Result<LmsSetupTestResult> result = this.restService.getBuilder(TestLmsSetup.class)
|
final Result<LmsSetup> call = this.restService.getBuilder(GetLmsSetup.class)
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId))
|
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId))
|
||||||
.call();
|
.call();
|
||||||
|
|
||||||
if (result.hasError()) {
|
if (call.hasError()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final LmsSetupTestResult lmsSetupTestResult = result.get();
|
final LmsSetup lmsSetup = call.get();
|
||||||
if (!lmsSetupTestResult.lmsType.features.contains(LmsSetup.Features.SEB_RESTRICTION)) {
|
return (lmsSetup.getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !lmsSetupTestResult.hasError(ErrorType.QUIZ_RESTRICTION_API_REQUEST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showConsistencyChecks(
|
private void showConsistencyChecks(
|
||||||
|
|
|
@ -90,7 +90,7 @@ import ch.ethz.seb.sebserver.gui.service.session.MonitoringEntry;
|
||||||
@GuiProfile
|
@GuiProfile
|
||||||
/** Defines functionality to get resources or functions of resources to feed e.g. selection or
|
/** Defines functionality to get resources or functions of resources to feed e.g. selection or
|
||||||
* combo-box content.
|
* combo-box content.
|
||||||
* */
|
*/
|
||||||
public class ResourceService {
|
public class ResourceService {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ResourceService.class);
|
private static final Logger log = LoggerFactory.getLogger(ResourceService.class);
|
||||||
|
@ -653,7 +653,7 @@ public class ResourceService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String localizedClientConnectionStatusName(final ConnectionStatus status) {
|
public String localizedClientConnectionStatusName(final ConnectionStatus status) {
|
||||||
String name;
|
final String name;
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
name = status.name();
|
name = status.name();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -116,6 +116,10 @@ public class UserServiceImpl implements UserService {
|
||||||
public SEBServerUser extract(final Principal principal) {
|
public SEBServerUser extract(final Principal principal) {
|
||||||
if (principal instanceof OAuth2Authentication) {
|
if (principal instanceof OAuth2Authentication) {
|
||||||
final Authentication userAuthentication = ((OAuth2Authentication) principal).getUserAuthentication();
|
final Authentication userAuthentication = ((OAuth2Authentication) principal).getUserAuthentication();
|
||||||
|
if (userAuthentication == null) {
|
||||||
|
// check if lms integration client
|
||||||
|
return isLMSIntegrationClient(principal);
|
||||||
|
}
|
||||||
if (userAuthentication instanceof UsernamePasswordAuthenticationToken) {
|
if (userAuthentication instanceof UsernamePasswordAuthenticationToken) {
|
||||||
final Object userPrincipal = userAuthentication.getPrincipal();
|
final Object userPrincipal = userAuthentication.getPrincipal();
|
||||||
if (userPrincipal instanceof SEBServerUser) {
|
if (userPrincipal instanceof SEBServerUser) {
|
||||||
|
@ -128,6 +132,26 @@ public class UserServiceImpl implements UserService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static SEBServerUser isLMSIntegrationClient(final Principal principal) {
|
||||||
|
final String name = principal.getName();
|
||||||
|
if ("lmsClient".equals(name)) {
|
||||||
|
return new SEBServerUser(
|
||||||
|
-1L,
|
||||||
|
new UserInfo("LMS_INTEGRATION_CLIENT", -1L, null, "lmsIntegrationClient", "lmsIntegrationClient", "lmsIntegrationClient", null,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null, null,
|
||||||
|
Arrays.stream(UserRole.values())
|
||||||
|
.map(Enum::name)
|
||||||
|
.collect(Collectors.toSet()),
|
||||||
|
Collections.emptyList(),
|
||||||
|
Collections.emptyList()),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Separated thread strategy
|
// 2. Separated thread strategy
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
|
|
@ -99,6 +99,14 @@ public class DeleteExamAction implements BatchActionExec {
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Result<EntityKey> deleteExamFromLMSIntegration(final Exam exam) {
|
||||||
|
return deleteExamDependencies(exam)
|
||||||
|
.flatMap(this::deleteExamWithRefs)
|
||||||
|
.map(Exam::getEntityKey)
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
private Result<Exam> deleteExamDependencies(final Exam entity) {
|
private Result<Exam> deleteExamDependencies(final Exam entity) {
|
||||||
return this.clientConnectionDAO.deleteAllForExam(entity.id)
|
return this.clientConnectionDAO.deleteAllForExam(entity.id)
|
||||||
.map(this::logDelete)
|
.map(this::logDelete)
|
||||||
|
|
|
@ -126,10 +126,14 @@ public class ExamRecordDAO {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
if (StringUtils.isBlank(quizId)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
return this.examRecordMapper.selectByExample()
|
return this.examRecordMapper.selectByExample()
|
||||||
.where(
|
.where(
|
||||||
ExamRecordDynamicSqlSupport.externalId,
|
ExamRecordDynamicSqlSupport.externalId,
|
||||||
isEqualToWhenPresent(quizId))
|
isEqualTo(quizId))
|
||||||
.and(
|
.and(
|
||||||
ExamRecordDynamicSqlSupport.active,
|
ExamRecordDynamicSqlSupport.active,
|
||||||
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
|
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
|
||||||
|
|
|
@ -150,6 +150,4 @@ public interface ExamAdminService {
|
||||||
|
|
||||||
Result<Exam> applyQuitPassword(Exam exam);
|
Result<Exam> applyQuitPassword(Exam exam);
|
||||||
|
|
||||||
Result<Exam> findExamByLmsIdentity(String courseId, String quizId, String identity);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -385,37 +385,6 @@ 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(() -> {
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ public interface LmsAPIService {
|
||||||
* @return LmsAPITemplate for specified LmsSetup configuration */
|
* @return LmsAPITemplate for specified LmsSetup configuration */
|
||||||
Result<LmsAPITemplate> getLmsAPITemplate(String lmsSetupId);
|
Result<LmsAPITemplate> getLmsAPITemplate(String lmsSetupId);
|
||||||
|
|
||||||
/** use this to the the specified LmsAPITemplate.
|
/** use this to the specified LmsAPITemplate.
|
||||||
*
|
*
|
||||||
* @param template the LmsAPITemplate
|
* @param template the LmsAPITemplate
|
||||||
* @return LmsSetupTestResult containing list of errors if happened */
|
* @return LmsSetupTestResult containing list of errors if happened */
|
||||||
|
|
|
@ -10,15 +10,14 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
||||||
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.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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
@ -26,12 +25,14 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
|
||||||
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.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamTemplateDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.DeleteExamAction;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
|
||||||
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.ExamTemplateChangeEvent;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateChangeEvent;
|
||||||
|
@ -39,7 +40,10 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationServi
|
||||||
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.impl.moodle.MoodleUtils;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -57,33 +61,52 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
private static final Logger log = LoggerFactory.getLogger(FullLmsIntegrationServiceImpl.class);
|
private static final Logger log = LoggerFactory.getLogger(FullLmsIntegrationServiceImpl.class);
|
||||||
|
|
||||||
private final LmsSetupDAO lmsSetupDAO;
|
private final LmsSetupDAO lmsSetupDAO;
|
||||||
|
private final UserActivityLogDAO userActivityLogDAO;
|
||||||
|
private final SEBClientConfigDAO sebClientConfigDAO;
|
||||||
|
private final ClientConfigService clientConfigService;
|
||||||
|
private final DeleteExamAction deleteExamAction;
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPIService lmsAPIService;
|
||||||
private final ExamAdminService examAdminService;
|
private final ExamAdminService examAdminService;
|
||||||
|
private final ExamSessionService examSessionService;
|
||||||
private final ExamDAO examDAO;
|
private final ExamDAO examDAO;
|
||||||
private final ExamTemplateDAO examTemplateDAO;
|
private final ExamTemplateDAO examTemplateDAO;
|
||||||
private final WebserviceInfo webserviceInfo;
|
private final WebserviceInfo webserviceInfo;
|
||||||
|
private final String lmsAPIEndpoint;
|
||||||
|
private final UserService userService;
|
||||||
private final ClientCredentialsResourceDetails resource;
|
private final ClientCredentialsResourceDetails resource;
|
||||||
|
|
||||||
private final OAuth2RestTemplate restTemplate;
|
private final OAuth2RestTemplate restTemplate;
|
||||||
|
|
||||||
public FullLmsIntegrationServiceImpl(
|
public FullLmsIntegrationServiceImpl(
|
||||||
final LmsSetupDAO lmsSetupDAO,
|
final LmsSetupDAO lmsSetupDAO,
|
||||||
|
final UserActivityLogDAO userActivityLogDAO,
|
||||||
|
final SEBClientConfigDAO sebClientConfigDAO,
|
||||||
|
final ClientConfigService clientConfigService,
|
||||||
|
final DeleteExamAction deleteExamAction,
|
||||||
final LmsAPIService lmsAPIService,
|
final LmsAPIService lmsAPIService,
|
||||||
final ExamAdminService examAdminService,
|
final ExamAdminService examAdminService,
|
||||||
|
final ExamSessionService examSessionService,
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
final ExamTemplateDAO examTemplateDAO,
|
final ExamTemplateDAO examTemplateDAO,
|
||||||
final WebserviceInfo webserviceInfo,
|
final WebserviceInfo webserviceInfo,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
|
final UserService userService,
|
||||||
|
@Value("${sebserver.webservice.lms.api.endpoint}") final String lmsAPIEndpoint,
|
||||||
@Value("${sebserver.webservice.lms.api.clientId}") final String clientId,
|
@Value("${sebserver.webservice.lms.api.clientId}") final String clientId,
|
||||||
@Value("${sebserver.webservice.api.admin.clientSecret}") final String clientSecret) {
|
@Value("${sebserver.webservice.api.admin.clientSecret}") final String clientSecret) {
|
||||||
|
|
||||||
this.lmsSetupDAO = lmsSetupDAO;
|
this.lmsSetupDAO = lmsSetupDAO;
|
||||||
|
this.userActivityLogDAO = userActivityLogDAO;
|
||||||
|
this.sebClientConfigDAO = sebClientConfigDAO;
|
||||||
|
this.clientConfigService = clientConfigService;
|
||||||
|
this.deleteExamAction = deleteExamAction;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPIService = lmsAPIService;
|
||||||
this.examAdminService = examAdminService;
|
this.examAdminService = examAdminService;
|
||||||
|
this.examSessionService = examSessionService;
|
||||||
this.examDAO = examDAO;
|
this.examDAO = examDAO;
|
||||||
this.examTemplateDAO = examTemplateDAO;
|
this.examTemplateDAO = examTemplateDAO;
|
||||||
this.webserviceInfo = webserviceInfo;
|
this.webserviceInfo = webserviceInfo;
|
||||||
|
this.lmsAPIEndpoint = lmsAPIEndpoint;
|
||||||
|
this.userService = userService;
|
||||||
|
|
||||||
resource = new ClientCredentialsResourceDetails();
|
resource = new ClientCredentialsResourceDetails();
|
||||||
resource.setAccessTokenUri(webserviceInfo.getOAuthTokenURI());
|
resource.setAccessTokenUri(webserviceInfo.getOAuthTokenURI());
|
||||||
|
@ -163,7 +186,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
final IntegrationData data = new IntegrationData(
|
final IntegrationData data = new IntegrationData(
|
||||||
connectionId,
|
connectionId,
|
||||||
lmsSetup.name,
|
lmsSetup.name,
|
||||||
webserviceInfo.getExternalServerURL(),
|
getAPIRootURL(),
|
||||||
accessToken,
|
accessToken,
|
||||||
this.getIntegrationTemplates(lmsSetup.institutionId)
|
this.getIntegrationTemplates(lmsSetup.institutionId)
|
||||||
);
|
);
|
||||||
|
@ -181,6 +204,8 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Boolean> deleteFullLmsIntegration(final Long lmsSetupId) {
|
public Result<Boolean> deleteFullLmsIntegration(final Long lmsSetupId) {
|
||||||
return lmsSetupDAO
|
return lmsSetupDAO
|
||||||
|
@ -219,8 +244,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||||
.flatMap(lmsAPIService::getLmsAPITemplate)
|
.flatMap(lmsAPIService::getLmsAPITemplate)
|
||||||
.map(findQuizData(courseId, quizId))
|
.map(findQuizData(courseId, quizId))
|
||||||
.map(createExam(examTemplateId, quitPassword))
|
.map(createAccountAndExam(examTemplateId, quitPassword));
|
||||||
.map(this::createAdHocSupporterAccount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -229,8 +253,27 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
final String courseId,
|
final String courseId,
|
||||||
final String quizId) {
|
final String quizId) {
|
||||||
|
|
||||||
return findExam(courseId, quizId)
|
return lmsSetupDAO
|
||||||
.flatMap(exam -> examDAO.deleteOne(exam.id));
|
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||||
|
.flatMap(lmsAPIService::getLmsAPITemplate)
|
||||||
|
.map(findQuizData(courseId, quizId))
|
||||||
|
.flatMap(this::findExam)
|
||||||
|
.map(this::checkDeletion)
|
||||||
|
.map(this::logExamDeleted)
|
||||||
|
.flatMap(deleteExamAction::deleteExamFromLMSIntegration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Exam checkDeletion(final Exam exam) {
|
||||||
|
// TODO check if Exam can be deleted according to the Spec
|
||||||
|
|
||||||
|
// check if there are no active SEB client connections
|
||||||
|
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||||
|
throw new APIMessage.APIMessageException(
|
||||||
|
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||||
|
.of("Exam currently has active SEB Client connections."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return exam;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -244,7 +287,39 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
final String courseId,
|
final String courseId,
|
||||||
final String quizId,
|
final String quizId,
|
||||||
final OutputStream out) {
|
final OutputStream out) {
|
||||||
return Result.ofRuntimeError("TODO");
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
final Result<Exam> examResult = lmsSetupDAO
|
||||||
|
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||||
|
.flatMap(lmsAPIService::getLmsAPITemplate)
|
||||||
|
.map(findQuizData(courseId, quizId))
|
||||||
|
.flatMap(this::findExam);
|
||||||
|
|
||||||
|
if (examResult.hasError()) {
|
||||||
|
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("Exam not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Exam exam = examResult.get();
|
||||||
|
|
||||||
|
String connectionConfigId = exam.getAdditionalAttribute(Exam.ADDITIONAL_ATTR_DEFAULT_CONNECTION_CONFIGURATION);
|
||||||
|
if (StringUtils.isBlank(connectionConfigId)) {
|
||||||
|
connectionConfigId = this.sebClientConfigDAO
|
||||||
|
.all(exam.institutionId, true)
|
||||||
|
.map(all -> all.iterator().next())
|
||||||
|
.map(SEBClientConfig::getModelId)
|
||||||
|
.getOr(null);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(connectionConfigId)) {
|
||||||
|
return Result.ofRuntimeError("No active Connection Configuration found");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clientConfigService.exportSEBClientConfiguration(out, connectionConfigId, exam.id);
|
||||||
|
return Result.EMPTY;
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return Result.ofError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function<LmsAPITemplate, QuizData> findQuizData(
|
private Function<LmsAPITemplate, QuizData> findQuizData(
|
||||||
|
@ -260,42 +335,64 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
|
|
||||||
return lmsAPITemplate
|
return lmsAPITemplate
|
||||||
.getQuiz(internalQuizId)
|
.getQuiz(internalQuizId)
|
||||||
.getOrThrow();
|
.onError(error -> log.error("Failed to find quiz-data for id: {}", quizId))
|
||||||
|
// this is only for debugging until Moodle Plugin is ready
|
||||||
|
.getOr(new QuizData(
|
||||||
|
MoodleUtils.getInternalQuizId(quizId, courseId, "MoodlePluginMockQuiz", null),
|
||||||
|
lmsAPITemplate.lmsSetup().institutionId,
|
||||||
|
lmsAPITemplate.lmsSetup().id,
|
||||||
|
lmsAPITemplate.lmsSetup().lmsType,
|
||||||
|
"MoodlePluginMockQuiz",
|
||||||
|
"",
|
||||||
|
DateTime.now(),
|
||||||
|
DateTime.now().plusDays(1),
|
||||||
|
"https://mockmoodle/swvgfrwef.sdvw",
|
||||||
|
null
|
||||||
|
));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Exam> findExam(
|
private Result<Exam> findExam(final QuizData quizData) {
|
||||||
final String courseId,
|
return examDAO.byExternalIdLike(quizData.id);
|
||||||
final String quizId) {
|
|
||||||
|
|
||||||
final String externalIdLike = quizId + Constants.COLON + courseId + Constants.PERCENTAGE;
|
|
||||||
return examDAO.byExternalIdLike(externalIdLike);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function<QuizData, Exam> createExam(
|
private Function<QuizData, Exam> createAccountAndExam(
|
||||||
final String examTemplateId,
|
final String examTemplateId,
|
||||||
final String quitPassword) {
|
final String quitPassword) {
|
||||||
|
|
||||||
return quizData -> {
|
return quizData -> {
|
||||||
|
|
||||||
|
final SEBServerUser currentUser = userService.getCurrentUser();
|
||||||
|
|
||||||
|
// check if the exam has already been imported, If so return the existing exam
|
||||||
|
final Result<Exam> existingExam = findExam(quizData);
|
||||||
|
if (!existingExam.hasError()) {
|
||||||
|
// TODO do we need to check if ad-hoc account exists and if not, create one?
|
||||||
|
return existingExam.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// import exam
|
||||||
final POSTMapper post = new POSTMapper(null, null);
|
final POSTMapper post = new POSTMapper(null, null);
|
||||||
post.putIfAbsent(Domain.EXAM.ATTR_EXAM_TEMPLATE_ID, examTemplateId);
|
post.putIfAbsent(Domain.EXAM.ATTR_EXAM_TEMPLATE_ID, examTemplateId);
|
||||||
if (StringUtils.isNotBlank(quitPassword)) {
|
if (StringUtils.isNotBlank(quitPassword)) {
|
||||||
post.putIfAbsent(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword);
|
post.putIfAbsent(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Exam exam = new Exam(null, quizData, post);
|
final String accountUUID = createAdHocSupporterAccount(quizData);
|
||||||
|
post.putIfAbsent(Domain.EXAM.ATTR_OWNER, accountUUID);
|
||||||
|
|
||||||
|
final Exam exam = new Exam(null, quizData, post);
|
||||||
return examDAO
|
return examDAO
|
||||||
.createNew(exam)
|
.createNew(exam)
|
||||||
.flatMap(examAdminService::applyExamImportInitialization)
|
.flatMap(examAdminService::applyExamImportInitialization)
|
||||||
|
.map(this::logExamCreated)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Exam createAdHocSupporterAccount(final Exam exam) {
|
private String createAdHocSupporterAccount(final QuizData data) {
|
||||||
// TODO create an ad hoc supporter account for this exam and apply it to the exam
|
// TODO create an ad hoc supporter account for this exam and apply it to the exam
|
||||||
return exam;
|
return "mockAccountUUID";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteAdHocAccount(final Long examId) {
|
private void deleteAdHocAccount(final Long examId) {
|
||||||
|
@ -319,4 +416,22 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getAPIRootURL() {
|
||||||
|
return webserviceInfo.getExternalServerURL() + lmsAPIEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Exam logExamCreated(final Exam exam) {
|
||||||
|
this.userActivityLogDAO
|
||||||
|
.logCreate(exam)
|
||||||
|
.onError(error -> log.warn("Failed to log exam creation from LMS: {}", error.getMessage()));
|
||||||
|
return exam;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Exam logExamDeleted(final Exam exam) {
|
||||||
|
this.userActivityLogDAO
|
||||||
|
.logDelete(exam)
|
||||||
|
.onError(error -> log.warn("Failed to log exam deletion from LMS: {}", error.getMessage()));
|
||||||
|
return exam;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -38,11 +40,6 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
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.LmsSetupDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
|
||||||
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.LmsAPITemplateFactory;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.QuizLookupService;
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Service
|
@Service
|
||||||
|
@ -56,6 +53,7 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
private final ClientCredentialService clientCredentialService;
|
private final ClientCredentialService clientCredentialService;
|
||||||
private final QuizLookupService quizLookupService;
|
private final QuizLookupService quizLookupService;
|
||||||
private final EnumMap<LmsType, LmsAPITemplateFactory> templateFactories;
|
private final EnumMap<LmsType, LmsAPITemplateFactory> templateFactories;
|
||||||
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
private final Map<CacheKey, LmsAPITemplate> cache = new ConcurrentHashMap<>();
|
private final Map<CacheKey, LmsAPITemplate> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@ -64,17 +62,19 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
final LmsSetupDAO lmsSetupDAO,
|
final LmsSetupDAO lmsSetupDAO,
|
||||||
final ClientCredentialService clientCredentialService,
|
final ClientCredentialService clientCredentialService,
|
||||||
final QuizLookupService quizLookupService,
|
final QuizLookupService quizLookupService,
|
||||||
|
final ApplicationEventPublisher applicationEventPublisher,
|
||||||
final Collection<LmsAPITemplateFactory> lmsAPITemplateFactories) {
|
final Collection<LmsAPITemplateFactory> lmsAPITemplateFactories) {
|
||||||
|
|
||||||
this.webserviceInfo = webserviceInfo;
|
this.webserviceInfo = webserviceInfo;
|
||||||
this.lmsSetupDAO = lmsSetupDAO;
|
this.lmsSetupDAO = lmsSetupDAO;
|
||||||
this.clientCredentialService = clientCredentialService;
|
this.clientCredentialService = clientCredentialService;
|
||||||
this.quizLookupService = quizLookupService;
|
this.quizLookupService = quizLookupService;
|
||||||
|
this.applicationEventPublisher = applicationEventPublisher;
|
||||||
|
|
||||||
final Map<LmsType, LmsAPITemplateFactory> factories = lmsAPITemplateFactories
|
final Map<LmsType, LmsAPITemplateFactory> factories = lmsAPITemplateFactories
|
||||||
.stream()
|
.stream()
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
t -> t.lmsType(),
|
LmsAPITemplateFactory::lmsType,
|
||||||
Function.identity()));
|
Function.identity()));
|
||||||
this.templateFactories = new EnumMap<>(factories);
|
this.templateFactories = new EnumMap<>(factories);
|
||||||
}
|
}
|
||||||
|
@ -158,14 +158,31 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0));
|
this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0));
|
||||||
return lmsSetupTestResult;
|
return lmsSetupTestResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
|
if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
|
||||||
|
final Long lmsSetupId = template.lmsSetup().id;
|
||||||
final LmsSetupTestResult lmsSetupTestResult = template.testFullIntegrationAPI();
|
final LmsSetupTestResult lmsSetupTestResult = template.testFullIntegrationAPI();
|
||||||
if (!lmsSetupTestResult.isOk()) {
|
if (!lmsSetupTestResult.isOk()) {
|
||||||
this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0));
|
this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0));
|
||||||
|
this.lmsSetupDAO
|
||||||
|
.setIntegrationActive(lmsSetupId, false)
|
||||||
|
.onError(er -> log.error("Failed to mark LMS integration inactive", er));
|
||||||
return lmsSetupTestResult;
|
return lmsSetupTestResult;
|
||||||
|
} else {
|
||||||
|
// try to apply full integration with a change LMSSetup notification
|
||||||
|
try {
|
||||||
|
applicationEventPublisher.publishEvent(new LmsSetupChangeEvent(template.lmsSetup()));
|
||||||
|
return lmsSetupTestResult;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to apply full LMS integration on test attempt: lms: {} error: {}",
|
||||||
|
template.lmsSetup(),
|
||||||
|
e.getMessage());
|
||||||
|
return LmsSetupTestResult.ofFullIntegrationAPIError(
|
||||||
|
template.lmsSetup().lmsType,
|
||||||
|
"Failed to apply full LMS integration");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -270,7 +270,7 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
log.info("Redirect to login after unauthorized request");
|
log.info("Redirect to login after unauthorized request");
|
||||||
response.getOutputStream().println("{ \"error\": \"" + exception.getMessage() + "\" }");
|
response.getOutputStream().println("{ \"error\": \"" + exception.getMessage() + "\" }");
|
||||||
},
|
},
|
||||||
EXAM_API_RESOURCE_ID,
|
LMS_API_RESOURCE_ID,
|
||||||
apiEndpoint,
|
apiEndpoint,
|
||||||
true,
|
true,
|
||||||
4,
|
4,
|
||||||
|
|
|
@ -84,8 +84,8 @@ public class LmsIntegrationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
path = API.LMS_FULL_INTEGRATION_EXAM_ENDPOINT,
|
path = API.LMS_FULL_INTEGRATION_CONNECTION_CONFIG_ENDPOINT,
|
||||||
method = RequestMethod.DELETE,
|
method = RequestMethod.POST,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||||
public void getConnectionConfiguration(
|
public void getConnectionConfiguration(
|
||||||
|
|
Loading…
Reference in a new issue