diff --git a/pom.xml b/pom.xml
index bba665d8..9c987aba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
jar
- 1.5.1
+ 1.5.2
${sebserver-version}
${sebserver-version}
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/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java
index 74a5d295..4015b6cd 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java
@@ -224,6 +224,9 @@ public class EntityTable {
}
this.table.addListener(SWT.Selection, event -> {
if (this.multiselection != null && event.item != null) {
+ if (event.item == null || event.item.isDisposed()) {
+ return;
+ }
handleMultiSelection((TableItem) event.item);
event.doit = false;
}
@@ -527,6 +530,9 @@ public class EntityTable {
// first remove all rows if there are some
this.table.removeAll();
+ if (this.multiselection != null) {
+ this.multiselection.clear();
+ }
// get page data and create rows
final Page page = this.pageSupplier.newBuilder()
@@ -642,10 +648,19 @@ public class EntityTable {
@SuppressWarnings("unchecked")
private ROW getRowData(final TableItem item) {
+ if (item == null || item.isDisposed()) {
+ log.warn("Selected item is null or disposed: {}", item);
+ return null;
+ }
return (ROW) item.getData(TABLE_ROW_DATA);
}
private EntityKey getEntityKey(final TableItem item) {
+ if (item == null || item.isDisposed()) {
+ log.warn("Selected item is null or disposed: {}", item);
+ return null;
+ }
+
final ROW rowData = getRowData(item);
if (rowData instanceof Entity) {
return ((Entity) rowData).getEntityKey();
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 a5505674..20a5b5cb 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
@@ -536,6 +536,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())
@@ -545,7 +549,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 917ba0bc..040942e8 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 033e7e5f..4cb062fe 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
@@ -12,6 +12,7 @@ import java.util.Collections;
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;
@@ -28,6 +29,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;
@@ -38,6 +40,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;
@@ -138,7 +141,12 @@ class ExamUpdateHandler {
.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)) {
@@ -356,7 +364,8 @@ class ExamUpdateHandler {
!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);
@@ -376,6 +385,11 @@ class ExamUpdateHandler {
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;
}
@@ -479,4 +493,29 @@ class ExamUpdateHandler {
}
}
+ 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/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties
index b49bad3b..d2a040b4 100644
--- a/src/main/resources/config/application-dev-ws.properties
+++ b/src/main/resources/config/application-dev-ws.properties
@@ -25,7 +25,7 @@ sebserver.webservice.clean-db-on-startup=false
# webservice configuration
sebserver.init.adminaccount.gen-on-init=false
-sebserver.webservice.distributed=true
+sebserver.webservice.distributed=false
#sebserver.webservice.master.delay.threshold=10000
sebserver.webservice.http.external.scheme=http
sebserver.webservice.http.external.servername=localhost
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 6af4ca76..3873d6c2 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -111,6 +111,7 @@ sebserver.form.validation.fieldError.serverNotAvailable=No service seems to be a
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.
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)