diff --git a/pom.xml b/pom.xml index 8c522671..9b554dfa 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,6 @@ UTF-8 - diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java index 4ba77093..f89a62c8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java @@ -18,6 +18,7 @@ import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -451,31 +452,28 @@ public class QuizLookupList implements TemplateComposer { } } - private Composite warningPanel = null; - private void handelPageReload( final Composite notePanel, final EntityTable table) { if (table.isComplete()) { PageService.clearComposite(notePanel); - if (this.warningPanel != null) { - this.warningPanel.dispose(); - } - this.warningPanel = null; } else { - if (this.warningPanel != null && !this.warningPanel.isDisposed()) { - this.warningPanel.dispose(); + + final Control[] children = notePanel.getChildren(); + if (children != null && children.length > 0) { + return; } - this.warningPanel = this.widgetFactory.createWarningPanel(notePanel, 15, true); + final Composite warningPanel = this.widgetFactory.createWarningPanel(notePanel, 15, true); + warningPanel.setData("warningPanel", "TRUE"); this.widgetFactory.imageButton( ImageIcon.SWITCH, - this.warningPanel, + warningPanel, TEXT_FETCH_NOTE_TOOLTIP, event -> table.applyFilter()); - final Label text = new Label(this.warningPanel, SWT.NONE); + final Label text = new Label(warningPanel, SWT.NONE); text.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); text.setText(this.pageService.getI18nSupport().getText(TEXT_FETCH_NOTE)); final GridData gridData = new GridData(SWT.LEFT, SWT.FILL, true, true); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java index fc0965be..2e1dfbd8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java @@ -98,7 +98,7 @@ public class PasswordInput extends Composite { final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); passwordInput.setLayoutData(gridData); passwordInput.setText(value != null - ? Utils.escapeHTML_XML_EcmaScript(value) + ? value : StringUtils.EMPTY); if (!buildPassword) { passwordInput.setEditable(false); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java index 22046ba5..a7bca8e7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java @@ -531,6 +531,10 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme lmsSetup); } + final Set qIdSet = internalIds.stream() + .map(MoodleUtils::getQuizId) + .collect(Collectors.toSet()); + return getCoursesForIds(restTemplate, moodleCourseIds) .stream() .filter(courseData -> !courseData.quizzes.isEmpty()) @@ -540,7 +544,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme urlPrefix, this.prependShortCourseName) .stream() - .filter(q -> internalIds.contains(q.id))) + .filter(q -> qIdSet.contains(MoodleUtils.getQuizId(q.id)))) .collect(Collectors.toList()); } catch (final Exception e) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index c682527f..99b375eb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -163,6 +163,23 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm "lmsSetup:lmsClientsecret:notNull")); } + final Result restTemplateResult = getRestTemplate(); + if (restTemplateResult.hasError()) { + missingAttrs.add(APIMessage.fieldValidationError( + LMS_SETUP.ATTR_LMS_URL, + "lmsSetup:lmsUrl:url.noservice")); + } else { + final OlatLmsRestTemplate olatLmsRestTemplate = restTemplateResult.get(); + try { + olatLmsRestTemplate.testAuthentication(); + } catch (final Exception e) { + log.error("Failed to test Authentication: {}", e.getMessage()); + missingAttrs.add(APIMessage.fieldValidationError( + LMS_SETUP.ATTR_LMS_URL, + "lmsSetup:lmsUrl:url.noaccess")); + } + } + if (!missingAttrs.isEmpty()) { return LmsSetupTestResult.ofMissingAttributes(LmsType.OPEN_OLAT, missingAttrs); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java index 533a3acc..bad59306 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java @@ -32,6 +32,12 @@ public class OlatLmsRestTemplate extends RestTemplate { private String token; private ClientCredentialsResourceDetails details; + public void testAuthentication() { + if (this.token == null) { + authenticate(); + } + } + public OlatLmsRestTemplate(final ClientCredentialsResourceDetails details) { super(); this.details = details; @@ -59,6 +65,7 @@ public class OlatLmsRestTemplate extends RestTemplate { return execution.execute(request, body); } + // otherwise, add the X-OLAT-TOKEN request.getHeaders().set("accept", "application/json"); request.getHeaders().set("X-OLAT-TOKEN", OlatLmsRestTemplate.this.token); @@ -85,6 +92,7 @@ public class OlatLmsRestTemplate extends RestTemplate { log.debug("OLAT [retry API call]: URL {}", request.getURI()); } + response.close(); response = execution.execute(request, body); if (log.isDebugEnabled()) { @@ -97,7 +105,7 @@ public class OlatLmsRestTemplate extends RestTemplate { } catch (final Exception e) { // TODO find a way to better deal with Olat temporary unavailability - log.error("Unexpected error: ", e); + log.error("Unexpected error: {}", e.getMessage()); throw e; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java index 3a78d902..9dc10f61 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.joda.time.DateTime; @@ -30,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -40,6 +42,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamResetEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamStartedEvent; @@ -167,7 +170,12 @@ class ExamUpdateHandler implements ExamUpdateTask { .forEach(quiz -> { try { - final Exam exam = exams.get(quiz.id); + final Exam exam = getExamForQuizWithMoodleSpecialCase(exams, quiz); + + if (exam == null) { + log.warn("Failed to find map exam to fetched quiz-data: {}", quiz); + return; + } if (hasChanges(exam, quiz)) { @@ -389,7 +397,8 @@ class ExamUpdateHandler implements ExamUpdateTask { !Objects.equals(exam.startTime, quizData.startTime) || !Objects.equals(exam.endTime, quizData.endTime) || !Utils.isEqualsWithEmptyCheckTruncated(exam.getDescription(), quizData.description) || - !Utils.isEqualsWithEmptyCheck(exam.getStartURL(), quizData.startURL)) { + !Utils.isEqualsWithEmptyCheck(exam.getStartURL(), quizData.startURL) || + !Objects.equals(exam.externalId, quizData.id)) { if (!Utils.isEqualsWithEmptyCheck(exam.name, quizData.name)) { log.info("Update name difference from LMS. Exam: {}, QuizData: {}", exam.name, quizData.name); @@ -409,6 +418,11 @@ class ExamUpdateHandler implements ExamUpdateTask { exam.getStartURL(), quizData.startURL); } + if (!Objects.equals(exam.externalId, quizData.id)) { + log.info("Update quizId difference from LMS. Exam:{}, QuizData: {}", + exam.externalId, + quizData.id); + } return true; } @@ -579,4 +593,33 @@ class ExamUpdateHandler implements ExamUpdateTask { } } + /** NOTE: LMS binding for Moodle uses a composed quiz-identifier with also contains the course short name + * for the reason to be able to re-identify a quiz when the main quiz-id changes in case of backup-restore on Moodle + * But since course names also can change this function tries to find old Exam mappings for course-names hat has + * changed */ + private Exam getExamForQuizWithMoodleSpecialCase(final Map exams, final QuizData quiz) { + Exam exam = exams.get(quiz.id); + + if (exam == null) { + try { + final LmsAPITemplate lms = this.lmsAPIService + .getLmsAPITemplate(quiz.lmsSetupId) + .getOrThrow(); + + if (lms.getType() == LmsType.MOODLE || lms.getType() == LmsType.MOODLE_PLUGIN) { + final String quizId = MoodleUtils.getQuizId(quiz.id); + final Optional find = + exams.keySet().stream().filter(key -> key.startsWith(quizId)).findFirst(); + if (find.isPresent()) { + exam = exams.get(find.get()); + } + } + } catch (final Exception e) { + log.error("Failed to verify changed external Exam id from moodle course: {}", e.getMessage()); + } + } + + return exam; + } + } diff --git a/src/main/resources/config/ehcache.xml b/src/main/resources/config/ehcache.xml index 81f2dddc..8f93fd14 100644 --- a/src/main/resources/config/ehcache.xml +++ b/src/main/resources/config/ehcache.xml @@ -33,8 +33,8 @@ 24 - - 100000 + + 100000 diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 464e36c7..b538491b 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -108,9 +108,16 @@ sebserver.form.validation.fieldError.invalidURL=The input does not match the URL sebserver.form.validation.fieldError.exists=This name already exists. Please choose another one sebserver.form.validation.fieldError.email=Invalid mail address sebserver.form.validation.fieldError.serverNotAvailable=No service seems to be available within the given URL +<<<<<<< HEAD sebserver.form.validation.fieldError.url.invalid=Invalid URL. The given URL cannot be reached sebserver.form.validation.fieldError.typeInvalid=This type is not implemented yet and cannot be used sebserver.form.validation.fieldError.url.noservice=The expected service is not available within the given URL and API access +======= +sebserver.form.validation.fieldError.url.invalid=Invalid URL. The given URL cannot be reached. +sebserver.form.validation.fieldError.typeInvalid=This type is not implemented yet and cannot be used. +sebserver.form.validation.fieldError.url.noservice=The expected service is not available within the given URL and API access. +sebserver.form.validation.fieldError.url.noaccess=There has no access been granted by the service. Please check the given access credentials. +>>>>>>> refs/remotes/origin/rel-1.5.2 sebserver.form.validation.fieldError.thresholdDuplicate=There are duplicate threshold values. sebserver.form.validation.fieldError.thresholdEmpty=There are missing values or colors for the threshold declaration sebserver.form.validation.fieldError.invalidIP=Invalid IP v4. Please enter a valid IP-address (v4)