SEBSERV-417 improved error and waning handling and logging
This commit is contained in:
parent
dc98f451fa
commit
c4dc211cb6
21 changed files with 344 additions and 84 deletions
|
@ -12,6 +12,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||
|
||||
public interface Activatable {
|
||||
|
||||
public enum ActivationAction {
|
||||
NONE,
|
||||
ACTIVATE,
|
||||
DEACTIVATE
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
boolean isActive();
|
||||
|
||||
|
|
|
@ -514,6 +514,11 @@ public class LmsSetupForm implements TemplateComposer {
|
|||
Utils.escapeHTML_XML_EcmaScript(error.message)));
|
||||
return onOK.apply(locTextKey);
|
||||
}
|
||||
case APPLY_FULL_INTEGRATION: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.fullintegration.error",
|
||||
Utils.formatHTMLLinesForceEscaped(Utils.escapeHTML_XML_EcmaScript(error.message))));
|
||||
}
|
||||
default: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.unknownError",
|
||||
|
|
|
@ -192,6 +192,7 @@ public class LmsSetupList implements TemplateComposer {
|
|||
.publishIf(userGrant::im, false)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_TOGGLE_ACTIVITY)
|
||||
.withAttribute(PageContext.CONTEXTUAL_ERROR_KEY, "sebserver.lmssetup.action.activation.error")
|
||||
.withSelect(
|
||||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
||||
this.pageService.activationToggleActionFunction(
|
||||
|
|
|
@ -52,6 +52,7 @@ public interface PageContext {
|
|||
|
||||
}
|
||||
|
||||
String CONTEXTUAL_ERROR_KEY = "ERROR_MESSAGE_KEY";
|
||||
/** The resource-bundle key of the generic load entity error message. */
|
||||
String GENERIC_LOAD_ERROR_TEXT_KEY = "sebserver.error.get.entity";
|
||||
String GENERIC_REMOVE_ERROR_TEXT_KEY = "sebserver.error.remove.entity";
|
||||
|
|
|
@ -326,9 +326,15 @@ public class PageServiceImpl implements PageService {
|
|||
|
||||
if (!errors.isEmpty()) {
|
||||
final String entityTypeName = this.resourceService.getEntityTypeName(entityType);
|
||||
final String errorMessageKey = action.pageContext().getAttribute(PageContext.CONTEXTUAL_ERROR_KEY);
|
||||
throw new MultiPageMessageException(
|
||||
new LocTextKey(PageContext.GENERIC_ACTIVATE_ERROR_TEXT_KEY, entityTypeName),
|
||||
new LocTextKey(
|
||||
(errorMessageKey != null)
|
||||
? errorMessageKey
|
||||
: PageContext.GENERIC_ACTIVATE_ERROR_TEXT_KEY,
|
||||
entityTypeName),
|
||||
errors);
|
||||
|
||||
}
|
||||
|
||||
return action;
|
||||
|
|
|
@ -144,9 +144,6 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
|
|||
|
||||
return this.lmsSetupRecordMapper.selectIdsByExample()
|
||||
.where(
|
||||
LmsSetupRecordDynamicSqlSupport.active,
|
||||
isEqualTo(1))
|
||||
.and(
|
||||
lmsType,
|
||||
isIn(types))
|
||||
.build()
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.Collection;
|
|||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
|
||||
|
@ -31,6 +32,8 @@ public interface FullLmsIntegrationService {
|
|||
@EventListener
|
||||
void notifyLmsSetupChange(final LmsSetupChangeEvent event);
|
||||
|
||||
Result<LmsSetup> applyLMSSetupDeactivation(LmsSetup lmsSetup);
|
||||
|
||||
@EventListener
|
||||
void notifyExamTemplateChange(final ExamTemplateChangeEvent event);
|
||||
@EventListener(ConnectionConfigurationChangeEvent.class)
|
||||
|
|
|
@ -10,7 +10,10 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
public interface SEBRestrictionService {
|
||||
|
||||
|
@ -67,4 +70,9 @@ public interface SEBRestrictionService {
|
|||
|
||||
Result<Exam> applyQuitPassword(final Exam exam);
|
||||
|
||||
@EventListener
|
||||
void notifyLmsSetupChange(final LmsSetupChangeEvent event);
|
||||
|
||||
Result<LmsSetup> applyLMSSetupDeactivation(LmsSetup lmsSetup);
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
|||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Activatable;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
|
@ -137,7 +138,6 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
.getClientHttpRequestFactory()
|
||||
.onSuccess(this.restTemplate::setRequestFactory)
|
||||
.onError(error -> log.warn("Failed to set HTTP request factory: ", error));
|
||||
//this.restTemplate.setErrorHandler(new OAuth2AuthorizationContextHolder.OAuth2AuthorizationContext.ErrorHandler(this.resource));
|
||||
this.restTemplate
|
||||
.getMessageConverters()
|
||||
.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
|
||||
|
@ -164,19 +164,46 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
return;
|
||||
}
|
||||
|
||||
if (lmsSetup.active) {
|
||||
if (event.activation == Activatable.ActivationAction.NONE) {
|
||||
if (!lmsSetup.integrationActive) {
|
||||
applyFullLmsIntegration(lmsSetup.id)
|
||||
.onError(error -> log.warn("Failed to update LMS integration for: {}", lmsSetup, error))
|
||||
.onError(error -> log.warn("Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage()))
|
||||
.onSuccess(data -> log.debug("Successfully updated LMS integration for: {} data: {}", lmsSetup, data));
|
||||
}
|
||||
} else if (lmsSetup.integrationActive) {
|
||||
deleteFullLmsIntegration(lmsSetup.id)
|
||||
.onError(error -> log.warn("Failed to delete LMS integration for: {}", lmsSetup, error))
|
||||
.onSuccess(data -> log.debug("Successfully deleted LMS integration for: {} data: {}", lmsSetup, data));
|
||||
} else if (event.activation == Activatable.ActivationAction.ACTIVATE) {
|
||||
applyFullLmsIntegration(lmsSetup.id)
|
||||
.map(data -> reapplyExistingExams(data,lmsSetup))
|
||||
.onError(error -> log.warn("Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage()))
|
||||
.onSuccess(data -> log.debug("Successfully updated LMS integration for: {} data: {}", lmsSetup, data));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<LmsSetup> applyLMSSetupDeactivation(final LmsSetup lmsSetup) {
|
||||
if (!lmsSetup.getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
|
||||
return Result.of(lmsSetup);
|
||||
}
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
// remove all active exam data for involved exams before deactivate them
|
||||
this.examDAO
|
||||
.allActiveForLMSSetup(Arrays.asList(lmsSetup.id))
|
||||
.getOrThrow()
|
||||
.forEach( exam -> {
|
||||
this.teacherAccountServiceImpl.deactivateTeacherAccountsForExam(exam)
|
||||
.map(e -> applyExamData(e, true))
|
||||
.onError(error -> log.warn("Failed delete teacher accounts for exam: {}", exam.name));
|
||||
});
|
||||
|
||||
// delete full integration on Moodle side before deactivate LMS Setup
|
||||
this.deleteFullLmsIntegration(lmsSetup.id)
|
||||
.getOrThrow();
|
||||
|
||||
return lmsSetup;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyExamTemplateChange(final ExamTemplateChangeEvent event) {
|
||||
final ExamTemplate examTemplate = event.getExamTemplate();
|
||||
|
@ -257,8 +284,6 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Result<Boolean> deleteFullLmsIntegration(final Long lmsSetupId) {
|
||||
return lmsSetupDAO
|
||||
|
@ -327,28 +352,24 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
|
||||
try {
|
||||
|
||||
// TODO this is hardcoded for Testing, below out-commented code is real business
|
||||
final Result<Exam> examResult = lmsSetupDAO
|
||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||
.flatMap(lmsAPITemplateCacheService::getLmsAPITemplate)
|
||||
.map(findQuizData(courseId, quizId))
|
||||
.flatMap(this::findExam);
|
||||
|
||||
this.connectionConfigurationService.exportSEBClientConfiguration(out, "1", null);
|
||||
if (examResult.hasError()) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("Exam not found"));
|
||||
}
|
||||
|
||||
// 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();
|
||||
//
|
||||
// final String connectionConfigId = getConnectionConfigurationId(exam);
|
||||
// if (StringUtils.isBlank(connectionConfigId)) {
|
||||
// throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found"));
|
||||
// }
|
||||
//
|
||||
// this.connectionConfigurationService.exportSEBClientConfiguration(out, connectionConfigId, exam.id);
|
||||
final Exam exam = examResult.get();
|
||||
|
||||
final String connectionConfigId = getConnectionConfigurationId(exam);
|
||||
if (StringUtils.isBlank(connectionConfigId)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found"));
|
||||
}
|
||||
|
||||
this.connectionConfigurationService.exportSEBClientConfiguration(out, connectionConfigId, exam.id);
|
||||
return Result.EMPTY;
|
||||
|
||||
} catch (final Exception e) {
|
||||
|
@ -356,6 +377,17 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
}
|
||||
}
|
||||
|
||||
private LmsSetup reapplyExistingExams(
|
||||
final IntegrationData integrationData,
|
||||
final LmsSetup lmsSetup) {
|
||||
|
||||
examDAO.allActiveForLMSSetup(Arrays.asList(lmsSetup.id))
|
||||
.getOrThrow()
|
||||
.forEach(exam -> applyExamData(exam, false));
|
||||
|
||||
return lmsSetup;
|
||||
}
|
||||
|
||||
private String getConnectionConfigurationId(final Exam exam) {
|
||||
String connectionConfigId = exam.getAdditionalAttribute(Exam.ADDITIONAL_ATTR_DEFAULT_CONNECTION_CONFIGURATION);
|
||||
if (StringUtils.isBlank(connectionConfigId)) {
|
||||
|
|
|
@ -36,6 +36,8 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(LmsAPITemplateAdapter.class);
|
||||
|
||||
private static final int DEFAULT_ATTEMPTS = 1;
|
||||
|
||||
private final CourseAccessAPI courseAccessAPI;
|
||||
private final SEBRestrictionAPI sebRestrictionAPI;
|
||||
|
||||
|
@ -78,7 +80,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsTestRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsTestRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -92,7 +94,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsAccessRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsAccessRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -106,7 +108,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.applyExamDataRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.applyExamDataRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -120,7 +122,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsTestRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsTestRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -134,7 +136,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
|
||||
Integer.class,
|
||||
1),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.quizzesRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -148,7 +150,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
|
||||
Integer.class,
|
||||
1),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.quizzesRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -162,7 +164,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
|
||||
Integer.class,
|
||||
1),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.quizzesRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -176,7 +178,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.chaptersRequest.attempts",
|
||||
Integer.class,
|
||||
1),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.chaptersRequest.blockingTime",
|
||||
Long.class,
|
||||
|
@ -204,7 +206,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.sebrestriction.attempts",
|
||||
Integer.class,
|
||||
1),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.sebrestriction.blockingTime",
|
||||
Long.class,
|
||||
|
@ -218,7 +220,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.examRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
DEFAULT_ATTEMPTS),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.examRequest.blockingTime",
|
||||
Long.class,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.Activatable;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
|
@ -16,8 +17,11 @@ public class LmsSetupChangeEvent extends ApplicationEvent {
|
|||
|
||||
private static final long serialVersionUID = -7239994198026689531L;
|
||||
|
||||
public LmsSetupChangeEvent(final LmsSetup source) {
|
||||
public final Activatable.ActivationAction activation;
|
||||
|
||||
public LmsSetupChangeEvent(final LmsSetup source, final Activatable.ActivationAction activation) {
|
||||
super(source);
|
||||
this.activation = activation;
|
||||
}
|
||||
|
||||
public LmsSetup getLmsSetup() {
|
||||
|
|
|
@ -17,6 +17,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationServi
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateCacheService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsTestService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleResponseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -58,27 +59,8 @@ public class LmsTestServiceImpl implements LmsTestService {
|
|||
}
|
||||
}
|
||||
|
||||
if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
|
||||
final Long lmsSetupId = template.lmsSetup().id;
|
||||
final LmsSetupTestResult lmsSetupTestResult = template.testFullIntegrationAPI();
|
||||
if (!lmsSetupTestResult.isOk()) {
|
||||
lmsAPITemplateCacheService.clearCache(template.lmsSetup().getModelId());
|
||||
this.lmsSetupDAO
|
||||
.setIntegrationActive(lmsSetupId, false)
|
||||
.onError(er -> log.error("Failed to mark LMS integration inactive", er));
|
||||
return lmsSetupTestResult;
|
||||
} else {
|
||||
|
||||
final Result<FullLmsIntegrationService.IntegrationData> integrationDataResult = fullLmsIntegrationService
|
||||
.applyFullLmsIntegration(template.lmsSetup().id);
|
||||
|
||||
if (integrationDataResult.hasError()) {
|
||||
return LmsSetupTestResult.ofFullIntegrationAPIError(
|
||||
template.lmsSetup().lmsType,
|
||||
"Failed to apply full LMS integration");
|
||||
}
|
||||
}
|
||||
}
|
||||
final LmsSetupTestResult lmsSetupTestResult = fullIntegrationTest(template);
|
||||
if (lmsSetupTestResult != null) return lmsSetupTestResult;
|
||||
|
||||
return LmsSetupTestResult.ofOkay(template.lmsSetup().getLmsType());
|
||||
}
|
||||
|
@ -110,13 +92,44 @@ public class LmsTestServiceImpl implements LmsTestService {
|
|||
}
|
||||
}
|
||||
|
||||
if (lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
|
||||
final LmsSetupTestResult lmsSetupTestResult = lmsSetupTemplate.testFullIntegrationAPI();
|
||||
if (!lmsSetupTestResult.isOk()) {
|
||||
return lmsSetupTestResult;
|
||||
}
|
||||
}
|
||||
final LmsSetupTestResult lmsSetupTestResult = fullIntegrationTest(lmsSetupTemplate);
|
||||
if (lmsSetupTestResult != null) return lmsSetupTestResult;
|
||||
|
||||
return LmsSetupTestResult.ofOkay(lmsSetupTemplate.lmsSetup().getLmsType());
|
||||
}
|
||||
|
||||
private LmsSetupTestResult fullIntegrationTest(final LmsAPITemplate template) {
|
||||
if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
|
||||
final Long lmsSetupId = template.lmsSetup().id;
|
||||
final LmsSetupTestResult lmsSetupTestResult = template.testFullIntegrationAPI();
|
||||
if (!lmsSetupTestResult.isOk()) {
|
||||
lmsAPITemplateCacheService.clearCache(template.lmsSetup().getModelId());
|
||||
this.lmsSetupDAO
|
||||
.setIntegrationActive(lmsSetupId, false)
|
||||
.onError(er -> log.error("Failed to mark LMS integration inactive", er));
|
||||
return lmsSetupTestResult;
|
||||
} else {
|
||||
|
||||
final Result<FullLmsIntegrationService.IntegrationData> integrationDataResult = fullLmsIntegrationService
|
||||
.applyFullLmsIntegration(template.lmsSetup().id);
|
||||
|
||||
if (integrationDataResult.hasError()) {
|
||||
Throwable error = integrationDataResult.getError();
|
||||
if (error instanceof RuntimeException) {
|
||||
error = error.getCause();
|
||||
}
|
||||
if (error != null && error instanceof MoodleResponseException) {
|
||||
return LmsSetupTestResult.ofFullIntegrationAPIError(
|
||||
template.lmsSetup().lmsType,
|
||||
error.getMessage());
|
||||
} else {
|
||||
return LmsSetupTestResult.ofFullIntegrationAPIError(
|
||||
template.lmsSetup().lmsType,
|
||||
"Failed to apply full LMS integration");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.Activatable;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -106,6 +107,53 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
.onError(t -> log.error("Failed to quit password for Exam: {}", exam, t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyLmsSetupChange(final LmsSetupChangeEvent event) {
|
||||
final LmsSetup lmsSetup = event.getLmsSetup();
|
||||
// only relevant for LMS Setups with SEB restriction feature
|
||||
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (event.activation == Activatable.ActivationAction.ACTIVATE) {
|
||||
examDAO.allActiveForLMSSetup(Arrays.asList(lmsSetup.id))
|
||||
.getOrThrow()
|
||||
.forEach(exam -> {
|
||||
try {
|
||||
this.applySEBRestrictionIfExamRunning(exam);
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to update SEB restriction for exam: {} error: {}", exam.name, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to update SEB restriction for re-activated exams: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<LmsSetup> applyLMSSetupDeactivation(final LmsSetup lmsSetup) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
// only relevant for LMS Setups with SEB restriction feature
|
||||
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
return lmsSetup;
|
||||
}
|
||||
|
||||
examDAO.allActiveForLMSSetup(Arrays.asList(lmsSetup.id))
|
||||
.getOrThrow()
|
||||
.forEach( exam -> {
|
||||
this.releaseSEBClientRestriction(exam)
|
||||
.onError(error -> log.warn(
|
||||
"Failed to release SEB Restriction for Exam: {} error: {}",
|
||||
exam.name,
|
||||
error.getMessage()));
|
||||
});
|
||||
|
||||
return lmsSetup;
|
||||
});
|
||||
}
|
||||
|
||||
private Exam applySEBRestrictionIfExamRunning(final Exam exam) {
|
||||
if (exam.status != Exam.ExamStatus.RUNNING) {
|
||||
return exam;
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -310,8 +312,6 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
return callMoodleAPIFunction(functionName, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String callMoodleAPIFunction(
|
||||
final String functionName,
|
||||
|
@ -338,6 +338,14 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
|
||||
final String body = createMoodleFormPostBody(queryAttributes);
|
||||
|
||||
// TODO remove this after testing
|
||||
try {
|
||||
final String uriString = URLDecoder.decode(queryParam.toUriString(), "UTF8");
|
||||
log.info("POST To Moodle URI (decoded UTF8): {}, body: {}", uriString, body);
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
final HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(
|
||||
HttpHeaders.CONTENT_TYPE,
|
||||
|
|
|
@ -105,9 +105,9 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
|
|||
final String connectionJSON = jsonMapper.writeValueAsString(data);
|
||||
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
|
||||
|
||||
//if (log.isDebugEnabled()) {
|
||||
log.info("Try to connect to Moodle Plugin 2.0 with: {}", connectionJSON);
|
||||
//}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Try to connect to Moodle Plugin 2.0 with: {}", connectionJSON);
|
||||
}
|
||||
|
||||
final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>();
|
||||
queryAttributes.add(ATTRIBUTE_CONNECTION, connectionJSON);
|
||||
|
@ -141,17 +141,19 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
|
|||
fullConnectionApplyResponse.warnings.stream()
|
||||
.filter(w -> Objects.equals(w.warningcode, "connectiondoesntmatch"))
|
||||
.findFirst()
|
||||
.ifPresent( w -> {
|
||||
|
||||
throw new MoodleResponseException("Failed to apply SEB Server connection due to connection mismatch", response);
|
||||
});
|
||||
.ifPresent(w -> {
|
||||
throw new MoodleResponseException("Failed to apply SEB Server connection due to connection mismatch\n There seems to be another SEB Server already connected to this LMS instance", response);
|
||||
});
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Got warnings from Moodle: {}", response);
|
||||
}
|
||||
} catch (final MoodleResponseException mre) {
|
||||
throw mre;
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to parse Moodle warnings. Error: {}", e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||
|
||||
/** Defines a exam update task. Exam update tasks are called in a fixed time interval on the master
|
||||
* Webservice instance to update various exam data like state, LMS data and so on.
|
||||
* A ExamUpdateTask can define a processing order on with the overall scheduler acts. Lower order first processed.
|
||||
*/
|
||||
public interface ExamUpdateTask {
|
||||
|
||||
int examUpdateTaskProcessingOrder();
|
||||
|
|
|
@ -218,7 +218,12 @@ public abstract class ActivatableEntityController<T extends GrantEntity & Activa
|
|||
(active) ? BulkActionType.ACTIVATE : BulkActionType.DEACTIVATE,
|
||||
entityType,
|
||||
new EntityName(modelId, entityType, entity.getName())));
|
||||
this.notifySaved(this.entityDAO.byModelId(entity.getModelId()).getOrThrow());
|
||||
final T savedEntity = this.entityDAO.byModelId(entity.getModelId()).getOrThrow();
|
||||
this.notifySaved(
|
||||
savedEntity,
|
||||
active
|
||||
? Activatable.ActivationAction.ACTIVATE
|
||||
: Activatable.ActivationAction.DEACTIVATE);
|
||||
return createReport;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -703,6 +703,10 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
|
|||
}
|
||||
|
||||
protected Result<T> notifySaved(final T entity) {
|
||||
return notifySaved(entity, Activatable.ActivationAction.NONE);
|
||||
}
|
||||
|
||||
protected Result<T> notifySaved(final T entity, final Activatable.ActivationAction activationAction) {
|
||||
return Result.of(entity);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,13 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
|||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.Activatable;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsTestService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
@ -50,8 +55,12 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.LMS_SETUP_ENDPOINT)
|
||||
public class LmsSetupController extends ActivatableEntityController<LmsSetup, LmsSetup> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LmsSetupController.class);
|
||||
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final LmsTestService lmsTestService;
|
||||
private final SEBRestrictionService sebRestrictionService;
|
||||
private final FullLmsIntegrationService fullLmsIntegrationService;
|
||||
final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
public LmsSetupController(
|
||||
|
@ -63,6 +72,8 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
|||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final LmsTestService lmsTestService,
|
||||
final SEBRestrictionService sebRestrictionService,
|
||||
final FullLmsIntegrationService fullLmsIntegrationService,
|
||||
final ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
super(authorization,
|
||||
|
@ -74,6 +85,8 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
|||
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.lmsTestService = lmsTestService;
|
||||
this.sebRestrictionService = sebRestrictionService;
|
||||
this.fullLmsIntegrationService = fullLmsIntegrationService;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
|
@ -148,9 +161,43 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Result<LmsSetup> notifySaved(final LmsSetup entity) {
|
||||
this.applicationEventPublisher.publishEvent(new LmsSetupChangeEvent(entity));
|
||||
return super.notifySaved(entity);
|
||||
protected Result<LmsSetup> notifySaved(final LmsSetup entity, final Activatable.ActivationAction activation) {
|
||||
this.applicationEventPublisher.publishEvent(new LmsSetupChangeEvent(entity, activation));
|
||||
return super.notifySaved(entity, activation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<LmsSetup> validForActivation(final LmsSetup entity, final boolean activation) {
|
||||
return super.validForActivation(entity, activation)
|
||||
.map(lmsSetup -> {
|
||||
if (!activation) {
|
||||
// on deactivation remove all SEB restrictions and delete full integration if in place
|
||||
return sebRestrictionService
|
||||
.applyLMSSetupDeactivation(lmsSetup)
|
||||
.flatMap(fullLmsIntegrationService::applyLMSSetupDeactivation)
|
||||
.getOrThrow();
|
||||
}
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<LmsSetup> validForDelete(final LmsSetup entity) {
|
||||
return Result.tryCatch(() -> {
|
||||
// if there is a SEB Restriction involved, release all SEB Restriction for exams
|
||||
if (entity.lmsType.features.contains(LmsSetup.Features.SEB_RESTRICTION)) {
|
||||
sebRestrictionService
|
||||
.applyLMSSetupDeactivation(entity)
|
||||
.getOrThrow();
|
||||
}
|
||||
// if there is a full LMS integration involved, delete it first on LMS
|
||||
if (entity.lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
|
||||
fullLmsIntegrationService
|
||||
.deleteFullLmsIntegration(entity.id)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -347,6 +347,8 @@ sebserver.lmssetup.type.OPEN_EDX=Open edX
|
|||
sebserver.lmssetup.type.ANS_DELFT=Ans Delft
|
||||
sebserver.lmssetup.type.OPEN_OLAT=OLAT
|
||||
|
||||
|
||||
|
||||
sebserver.lmssetup.list.actions=
|
||||
sebserver.lmssetup.list.action.no.modify.privilege=No Access: A Assessment Tool Setup from other institution cannot be modified.
|
||||
sebserver.lmssetup.list.empty=No Assessment Tool Setup can be found. Please adapt the filter or create a new Assessment Tool Setup
|
||||
|
@ -378,6 +380,7 @@ sebserver.lmssetup.action.test.tokenRequestError=The API access was denied:<br/>
|
|||
sebserver.lmssetup.action.test.quizRequestError=Unable to request courses or exams from the course API of the Assessment Tool. {0}
|
||||
sebserver.lmssetup.action.test.quizRestrictionError=Unable to access course restriction API of the Assessment Tool. {0}
|
||||
sebserver.lmssetup.action.test.features.error=The API access was granted but there is some missing functionality:<br/><br/>- Course Access: {0}<br/>- SEB Restriction: {1}
|
||||
sebserver.lmssetup.action.test.fullintegration.error=Full Assessment Tool Integration failed due to:<br/><br/> {0}
|
||||
sebserver.lmssetup.action.test.missingParameter=There is one or more missing connection parameter.<br/>Please check the connection parameter for this Assessment Tool Setup
|
||||
sebserver.lmssetup.action.test.unknownError=An unexpected error happened while trying to connect to the Assessment Tool course API. {0}
|
||||
sebserver.lmssetup.action.test.quizRequestError.moodle.missing.plugin=Moodle SEB Server integration plugin cannot be detected on this Moodle server.<br/>Please consider using the origin "Moodle" Assessment Tool Setup type or install the SEB Server integration plugin on this Moodle server.<br/><br/>For further information please refer to the <a target="_blank" href="https://seb-server.readthedocs.io/en/latest/#">documentation</a>
|
||||
|
@ -387,6 +390,7 @@ sebserver.lmssetup.action.save=Save Assessment Tool Setup
|
|||
sebserver.lmssetup.action.activate=Activate Assessment Tool Setup
|
||||
sebserver.lmssetup.action.deactivate=Deactivate Assessment Tool Setup
|
||||
sebserver.lmssetup.action.delete=Delete Assessment Tool Setup
|
||||
sebserver.lmssetup.action.activation.error=Failed to activate/deactivate Assessment Tool Setup.<br/>Please open the Assessment Tool Setup and test the connection and make sure connection data is correct.
|
||||
|
||||
sebserver.lmssetup.info.pleaseSelect=At first please select an Assessment Tool Setup from the list
|
||||
|
||||
|
|
60
src/test/java/ch/ethz/seb/sebserver/gbl/JSONParserTest.java
Normal file
60
src/test/java/ch/ethz/seb/sebserver/gbl/JSONParserTest.java
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.gbl;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JSONParserTest {
|
||||
|
||||
@Test
|
||||
public void testSpecialChar1() throws JsonProcessingException {
|
||||
final String json = """
|
||||
{"meta_data": {
|
||||
"param1": "value—dvgswg",
|
||||
"param2": "value2-dvgswg",
|
||||
"param3": "value3",
|
||||
"param4": "value4-%ç/&=&&çETZGFIUZHàPIHBNHK VG$ä$à£à!èéLèPLIOU=(&&(Rç%çE"
|
||||
}}""";
|
||||
|
||||
final JSONMapper jsonMapper = new JSONMapper();
|
||||
final MetaData metaData = jsonMapper.readValue(json, MetaData.class);
|
||||
assertEquals("MetaData{meta_data={param1=value—dvgswg, param2=value2-dvgswg, param3=value3, param4=value4-%ç/&=&&çETZGFIUZHàPIHBNHK VG$ä$à£à!èéLèPLIOU=(&&(Rç%çE}}", metaData.toString());
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static final class MetaData {
|
||||
@JsonProperty("meta_data")
|
||||
public final Map<String, String> meta_data;
|
||||
|
||||
|
||||
@JsonCreator
|
||||
private MetaData(
|
||||
@JsonProperty("meta_data") final Map<String, String> metaData) {
|
||||
|
||||
meta_data = metaData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetaData{" +
|
||||
"meta_data=" + meta_data +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue