SEBSERV-417 improved error and waning handling and logging

This commit is contained in:
anhefti 2024-06-11 13:13:02 +02:00
parent dc98f451fa
commit c4dc211cb6
21 changed files with 344 additions and 84 deletions

View file

@ -12,6 +12,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
public interface Activatable {
public enum ActivationAction {
NONE,
ACTIVATE,
DEACTIVATE
}
@JsonIgnore
boolean isActive();

View file

@ -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",

View file

@ -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(

View file

@ -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";

View file

@ -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;

View file

@ -144,9 +144,6 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
return this.lmsSetupRecordMapper.selectIdsByExample()
.where(
LmsSetupRecordDynamicSqlSupport.active,
isEqualTo(1))
.and(
lmsType,
isIn(types))
.build()

View file

@ -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)

View file

@ -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);
}

View file

@ -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)) {

View file

@ -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,

View file

@ -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() {

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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,

View file

@ -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()) {

View file

@ -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();

View file

@ -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;
});
}

View file

@ -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);
}

View file

@ -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;
});
}
}

View file

@ -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

View 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 +
'}';
}
}
}