diff --git a/README.rst b/README.rst
index c843877a..1087fb53 100644
--- a/README.rst
+++ b/README.rst
@@ -101,6 +101,11 @@ Docker-Image:
- Exact release version: docker pull anhefti/seb-server:v1.5.0 (sha256:21d62e24dd5cf697ab5f2b437dc458e6c7492ea294f77a424d39d05164d6c8cc)
- Latest stable minor version with patches: docker pull anhefti/seb-server:v1.5-stable
+Latest Version is 1.5.1 with Docker-Image:
+
+- Exact release version: docker pull anhefti/seb-server:v1.5.1 (sha256:a866faa18848d15301e9f06d17aab1c7293d2a27d967038d32410f817e478408)
+- Latest stable minor version with patches: docker pull anhefti/seb-server:v1.5-latest
+
SEB - SEB Server Compatibility
------------------------------
diff --git a/pom.xml b/pom.xml
index a8627936..bba665d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
jar
- 1.5.0
+ 1.5.1
${sebserver-version}
${sebserver-version}
UTF-8
diff --git a/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java b/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java
index 5272843e..3fd16e75 100644
--- a/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java
@@ -76,7 +76,7 @@ public class ClientHttpRequestFactoryService {
final ClientCredentialService clientCredentialService,
@Value("${sebserver.http.client.connect-timeout:15000}") final int connectTimeout,
@Value("${sebserver.http.client.connection-request-timeout:20000}") final int connectionRequestTimeout,
- @Value("${sebserver.http.client.read-timeout:20000}") final int readTimeout) {
+ @Value("${sebserver.http.client.read-timeout:30000}") final int readTimeout) {
this.environment = environment;
this.clientCredentialService = clientCredentialService;
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java
index 5ec0af5d..c039a91d 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java
@@ -16,6 +16,7 @@ import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
+import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
@@ -161,7 +162,7 @@ public class ExamDeletePopup {
new ActionEvent(action),
action.pageContext());
- final String examName = examToDelete.toName().name;
+ final String examName = StringEscapeUtils.escapeXml11(examToDelete.toName().name);
final List dependencies = report.results.stream()
.filter(key -> !key.equals(entityKey))
.collect(Collectors.toList());
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 c95b2c99..4ba77093 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
@@ -59,6 +59,7 @@ import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
@Lazy
@Component
@@ -109,6 +110,8 @@ public class QuizLookupList implements TemplateComposer {
new LocTextKey("sebserver.quizdiscovery.quiz.import.existing");
private final static LocTextKey TEXT_FETCH_NOTE =
new LocTextKey("sebserver.quizdiscovery.list.fetchnote");
+ private final static LocTextKey TEXT_FETCH_NOTE_TOOLTIP =
+ new LocTextKey("sebserver.quizdiscovery.list.fetchnote.tooltip");
private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX =
"sebserver.quizdiscovery.quiz.details.additional.";
@@ -448,7 +451,7 @@ public class QuizLookupList implements TemplateComposer {
}
}
- private boolean showingFetchNote = false;
+ private Composite warningPanel = null;
private void handelPageReload(
final Composite notePanel,
@@ -456,29 +459,28 @@ public class QuizLookupList implements TemplateComposer {
if (table.isComplete()) {
PageService.clearComposite(notePanel);
- this.showingFetchNote = false;
- } else {
- if (!this.showingFetchNote) {
- final Composite warningPanel = this.widgetFactory.createWarningPanel(notePanel, 15, true);
- GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, false, true);
- gridData.heightHint = 28;
- gridData.widthHint = 25;
- gridData.verticalIndent = 5;
- final Label action = new Label(warningPanel, SWT.NONE);
- action.setImage(WidgetFactory.ImageIcon.SWITCH.getImage(notePanel.getDisplay()));
- action.setLayoutData(gridData);
- action.addListener(SWT.MouseDown, event -> {
- table.applyFilter();
- });
-
- final Label text = new Label(warningPanel, SWT.NONE);
- text.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
- text.setText(this.pageService.getI18nSupport().getText(TEXT_FETCH_NOTE));
- gridData = new GridData(SWT.LEFT, SWT.FILL, true, true);
- gridData.heightHint = 16;
- text.setLayoutData(gridData);
- this.showingFetchNote = true;
+ if (this.warningPanel != null) {
+ this.warningPanel.dispose();
}
+ this.warningPanel = null;
+ } else {
+ if (this.warningPanel != null && !this.warningPanel.isDisposed()) {
+ this.warningPanel.dispose();
+ }
+
+ this.warningPanel = this.widgetFactory.createWarningPanel(notePanel, 15, true);
+ this.widgetFactory.imageButton(
+ ImageIcon.SWITCH,
+ this.warningPanel,
+ TEXT_FETCH_NOTE_TOOLTIP,
+ event -> table.applyFilter());
+
+ final Label text = new Label(this.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);
+ gridData.heightHint = 28;
+ text.setLayoutData(gridData);
}
notePanel.getParent().layout(true, true);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
index 530e0630..c33c5e50 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
@@ -18,6 +18,7 @@ import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
+import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
@@ -151,7 +152,9 @@ public class MonitoringRunningExam implements TemplateComposer {
final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
pageContext.getParent(),
- new LocTextKey("sebserver.monitoring.exam", exam.name));
+ new LocTextKey(
+ "sebserver.monitoring.exam",
+ StringEscapeUtils.escapeXml11(exam.name)));
final Composite tablePane = new Composite(content, SWT.NONE);
tablePane.setLayout(new GridLayout());
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/PolyglotPageServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/PolyglotPageServiceImpl.java
index 1ddf4989..bc1beaf2 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/PolyglotPageServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/PolyglotPageServiceImpl.java
@@ -270,7 +270,16 @@ public final class PolyglotPageServiceImpl implements PolyglotPageService {
return label -> {
if (locTextKey != null) {
- label.setText(i18nSupport.getText(locTextKey));
+ try {
+ label.setText(i18nSupport.getText(locTextKey));
+ } catch (final Exception e) {
+ label.setData(RWT.MARKUP_ENABLED, false);
+ try {
+ label.setText(i18nSupport.getText(locTextKey));
+ } catch (final Exception ee) {
+ label.setText(locTextKey.name);
+ }
+ }
}
if (i18nSupport.hasText(locToolTipKey)) {
label.setToolTipText(Utils.formatLineBreaks(i18nSupport.getText(locToolTipKey)));
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java
index 1c78953e..03149686 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java
@@ -185,6 +185,9 @@ public final class PageAction {
} catch (final PageMessageException pme) {
PageAction.this.pageContext.publishPageMessage(pme);
return;
+ } catch (final Exception e) {
+ this.pageContext.notifyUnexpectedError(e);
+ return;
}
} else {
callback.accept(exec());
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java
index c453c9bc..35790109 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java
@@ -56,14 +56,14 @@ public final class UpdateErrorHandler implements Function {
} catch (final Exception ee) {
log.warn("Unable to auto-logout: ", ee.getMessage());
}
- return true;
+ return false;
}
}
@Override
public Boolean apply(final Exception error) {
this.errors++;
- log.error("Failed to update server push: {}", error.getMessage(), error);
+ log.warn("Failed to update server push: {}", error.getMessage());
if (this.errors > 5) {
checkUserSession();
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java
index 8f1dd4c2..483e48cf 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java
@@ -180,7 +180,7 @@ public abstract class RestCall {
e,
"NO RESPONSE AVAILABLE",
String.valueOf(builder)));
- return Result.ofError(e);
+ return Result.ofError(restCallError);
}
}
@@ -216,6 +216,10 @@ public abstract class RestCall {
} else {
restCallError.errors.add(APIMessage.ErrorMessage.GENERIC.of(responseEntity.getBody()));
}
+ } catch (final Exception e) {
+ final String body = responseEntity.getBody();
+ log.error("Failed to parse rest response error message: {}", body);
+ throw e;
}
log.debug(
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java
index 9e766723..c33793d3 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java
@@ -371,7 +371,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
}
public void updateGUI() {
-
if (this.needsSort) {
sortTable();
}
@@ -725,10 +724,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
this.indicatorWeights[i] = -1;
}
}
+
+ this.monitoringData = monitoringData;
+
if (this.indicatorValueChanged) {
updateIndicatorWeight();
}
- this.monitoringData = monitoringData;
return this.staticData == null
|| this.staticData == ClientStaticData.NULL_DATA
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/Message.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/Message.java
index df09b0f0..4ad20c14 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/Message.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/Message.java
@@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.widget;
import java.util.Locale;
+import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.widgets.DialogCallback;
import org.eclipse.swt.graphics.Rectangle;
@@ -17,7 +18,6 @@ import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@@ -46,7 +46,7 @@ public final class Message extends MessageBox {
super.prepareOpen();
} catch (final IllegalArgumentException e) {
// fallback on markup text error
- super.setMessage(Utils.escapeHTML_XML_EcmaScript(super.getMessage()));
+ super.setMessage(StringEscapeUtils.escapeXml11(super.getMessage()));
super.prepareOpen();
}
final GridLayout layout = (GridLayout) super.shell.getLayout();
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java
index 27406d9c..ba0abd37 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java
@@ -814,10 +814,13 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.status,
- SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES))
+ SqlBuilder.isEqualTo(ConnectionStatus.ACTIVE.name()))
.and(
ClientConnectionRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
+ .and(
+ ClientConnectionRecordDynamicSqlSupport.clientVersion,
+ SqlBuilder.isNotNull())
.and(
ClientConnectionRecordDynamicSqlSupport.clientVersionGranted,
SqlBuilder.isNull())
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/QuizLookupServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/QuizLookupServiceImpl.java
index e74deed9..cc83fe2e 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/QuizLookupServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/QuizLookupServiceImpl.java
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@@ -53,16 +54,19 @@ public class QuizLookupServiceImpl implements QuizLookupService {
private final UserService userService;
private final LmsSetupDAO lmsSetupDAO;
private final AsyncRunner asyncRunner;
+ private final long fetchedDataValiditySeconds;
public QuizLookupServiceImpl(
final UserService userService,
final LmsSetupDAO lmsSetupDAO,
final AsyncService asyncService,
- final Environment environment) {
+ final Environment environment,
+ @Value("${sebserver.webservice.lms.datafetch.validity.seconds:600}") final long fetchedDataValiditySeconds) {
this.userService = userService;
this.lmsSetupDAO = lmsSetupDAO;
this.asyncRunner = asyncService.getAsyncRunner();
+ this.fetchedDataValiditySeconds = fetchedDataValiditySeconds;
}
@Override
@@ -158,7 +162,10 @@ public class QuizLookupServiceImpl implements QuizLookupService {
}
if (!asyncLookup.isValid(filterMap)) {
- this.lookups.remove(userId);
+ final AsyncLookup removed = this.lookups.remove(userId);
+ if (removed != null) {
+ removed.cancel();
+ }
this.createNewAsyncLookup(userId, filterMap, lmsAPITemplateSupplier);
}
@@ -198,7 +205,12 @@ public class QuizLookupServiceImpl implements QuizLookupService {
}
final LookupFilterCriteria criteria = new LookupFilterCriteria(filterMap);
- final AsyncLookup asyncLookup = new AsyncLookup(userInstitutionId, userId, criteria, buffers);
+ final AsyncLookup asyncLookup = new AsyncLookup(
+ userInstitutionId,
+ userId,
+ criteria,
+ buffers,
+ this.fetchedDataValiditySeconds);
if (log.isDebugEnabled()) {
log.debug("Create new AsyncLookup: user={} criteria={}", userId, criteria);
@@ -278,18 +290,21 @@ public class QuizLookupServiceImpl implements QuizLookupService {
final Collection asyncBuffers;
final long timeCreated;
long timeCompleted = Long.MAX_VALUE;
+ private final long fetchedDataValiditySeconds;
public AsyncLookup(
final long institutionId,
final String userId,
final LookupFilterCriteria lookupFilterCriteria,
- final Collection asyncBuffers) {
+ final Collection asyncBuffers,
+ final long fetchedDataValiditySeconds) {
this.institutionId = institutionId;
this.userId = userId;
this.lookupFilterCriteria = lookupFilterCriteria;
this.asyncBuffers = asyncBuffers;
this.timeCreated = Utils.getMillisecondsNow();
+ this.fetchedDataValiditySeconds = fetchedDataValiditySeconds;
}
LookupResult getAvailable() {
@@ -307,10 +322,7 @@ public class QuizLookupServiceImpl implements QuizLookupService {
boolean isUpToDate() {
final long now = Utils.getMillisecondsNow();
- if (now - this.timeCreated > 5 * Constants.MINUTE_IN_MILLIS) {
- return false;
- }
- if (now - this.timeCompleted > Constants.MINUTE_IN_MILLIS) {
+ if (now - this.timeCreated > this.fetchedDataValiditySeconds * Constants.SECOND_IN_MILLIS) {
return false;
}
return true;
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockCourseAccessAPI.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockCourseAccessAPI.java
index c8bf545a..b65da02a 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockCourseAccessAPI.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockCourseAccessAPI.java
@@ -63,7 +63,8 @@ public class MockCourseAccessAPI implements CourseAccessAPI {
"quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "Demo Quiz Mockup
",
"2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
- "quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP)", "Demo Quiz Mockup
",
+ "quiz2 äöüèÜÄÖ ?<", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP) äöüèÜÄÖ ?< ",
+ "Demo Quiz Mockup
",
"2020-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3 (MOCKUP)", "Demo Quiz Mockup
",
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 2e3a464e..a5505674 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
@@ -61,6 +61,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils.Courses;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils.CoursesPlugin;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils.MoodleUserDetails;
+import io.micrometer.core.instrument.util.StringUtils;
public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess implements CourseAccessAPI {
@@ -81,6 +82,9 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
public static final String PARAM_PAGE_START = "startneedle";
public static final String PARAM_PAGE_SIZE = "perpage";
+ public static final String SQL_QUIZ_NAME = "m.name";
+ public static final String SQL_COURSE_NAME = "shortname";
+
public static final String SQL_CONDITION_TEMPLATE =
//"(startdate >= %s or timecreated >=%s) and (enddate is null or enddate = 0 or enddate >= %s)";
"(startdate is null OR startdate = 0 OR startdate >= %s) AND (enddate is null or enddate = 0 OR enddate >= %s)";
@@ -92,6 +96,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
private final int pageSize;
private final int maxSize;
private final int cutoffTimeOffset;
+ private final boolean applyNameCriteria;
private MoodleAPIRestTemplate restTemplate;
@@ -100,11 +105,13 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
final AsyncService asyncService,
final MoodleRestTemplateFactory restTemplateFactory,
final CacheManager cacheManager,
- final Environment environment) {
+ final Environment environment,
+ final boolean applyNameCriteria) {
super(cacheManager);
this.jsonMapper = jsonMapper;
this.restTemplateFactory = restTemplateFactory;
+ this.applyNameCriteria = applyNameCriteria;
this.prependShortCourseName = BooleanUtils.toBoolean(environment.getProperty(
"sebserver.webservice.lms.moodle.prependShortCourseName",
@@ -118,7 +125,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
environment.getProperty(
"sebserver.webservice.circuitbreaker.moodleRestCall.blockingTime",
Long.class,
- Constants.SECOND_IN_MILLIS * 20),
+ Constants.SECOND_IN_MILLIS * 30),
environment.getProperty(
"sebserver.webservice.circuitbreaker.moodleRestCall.timeToRecover",
Long.class,
@@ -184,10 +191,11 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
quizFromTime = DateTime.now(DateTimeZone.UTC).minusYears(this.cutoffTimeOffset);
}
final Predicate quizFilter = LmsAPIService.quizFilterPredicate(filterMap);
+ final String quizName = filterMap.getQuizName();
while (!asyncQuizFetchBuffer.finished && !asyncQuizFetchBuffer.canceled) {
try {
- fetchQuizzesPage(page, quizFromTime, asyncQuizFetchBuffer, quizFilter);
+ fetchQuizzesPage(page, quizFromTime, quizName, asyncQuizFetchBuffer, quizFilter);
page++;
} catch (final Exception e) {
log.error("Unexpected error while trying to fetch moodle quiz page: {}", page, e);
@@ -371,6 +379,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
private void fetchQuizzesPage(
final int page,
final DateTime quizFromTime,
+ final String nameCondition,
final AsyncQuizFetchBuffer asyncQuizFetchBuffer,
final Predicate quizFilter) throws JsonParseException, JsonMappingException, IOException {
@@ -382,7 +391,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
: lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
final Collection fetchCoursesPage =
- fetchCoursesPage(restTemplate, quizFromTime, page, this.pageSize);
+ fetchCoursesPage(restTemplate, quizFromTime, nameCondition, page, this.pageSize);
// finish if page is empty (no courses left
if (fetchCoursesPage.isEmpty()) {
asyncQuizFetchBuffer.finish();
@@ -408,6 +417,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
private Collection fetchCoursesPage(
final MoodleAPIRestTemplate restTemplate,
final DateTime quizFromTime,
+ final String nameCondition,
final int page,
final int size) throws JsonParseException, JsonMappingException, IOException {
@@ -422,13 +432,25 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
final long defaultCutOff = Utils.toUnixTimeInSeconds(
DateTime.now(DateTimeZone.UTC).minusYears(this.cutoffTimeOffset));
final long cutoffDate = (filterDate < defaultCutOff) ? filterDate : defaultCutOff;
- final String sqlCondition = String.format(
+ String sqlCondition = String.format(
SQL_CONDITION_TEMPLATE,
String.valueOf(cutoffDate),
String.valueOf(filterDate));
final String fromElement = String.valueOf(page * size);
final LinkedMultiValueMap attributes = new LinkedMultiValueMap<>();
+ if (this.applyNameCriteria && StringUtils.isNotBlank(nameCondition)) {
+ sqlCondition = sqlCondition + " AND (" +
+ SQL_QUIZ_NAME +
+ " LIKE '" +
+ Utils.toSQLWildcard(nameCondition) +
+ "' OR " +
+ SQL_COURSE_NAME +
+ " LIKE '" +
+ Utils.toSQLWildcard(nameCondition) +
+ "')";
+ }
+
// Note: courseid[]=0 means all courses. Moodle don't like empty parameter
attributes.add(PARAM_COURSE_ID_ARRAY, "0");
attributes.add(PARAM_SQL_CONDITIONS, sqlCondition);
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MooldePluginLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MooldePluginLmsAPITemplateFactory.java
index b455e7b8..f83dbecf 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MooldePluginLmsAPITemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MooldePluginLmsAPITemplateFactory.java
@@ -46,6 +46,7 @@ public class MooldePluginLmsAPITemplateFactory implements LmsAPITemplateFactory
private final ExamConfigurationValueService examConfigurationValueService;
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
private final String[] alternativeTokenRequestPaths;
+ private final boolean applyNameCriteria;
protected MooldePluginLmsAPITemplateFactory(
final JSONMapper jsonMapper,
@@ -55,7 +56,8 @@ public class MooldePluginLmsAPITemplateFactory implements LmsAPITemplateFactory
final ClientCredentialService clientCredentialService,
final ExamConfigurationValueService examConfigurationValueService,
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
- @Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths) {
+ @Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths,
+ @Value("${sebserver.webservice.lms.moodle.fetch.applyNameCriteria:true}") final boolean applyNameCriteria) {
this.jsonMapper = jsonMapper;
this.cacheManager = cacheManager;
@@ -67,6 +69,7 @@ public class MooldePluginLmsAPITemplateFactory implements LmsAPITemplateFactory
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
? StringUtils.split(alternativeTokenRequestPaths, Constants.LIST_SEPARATOR)
: null;
+ this.applyNameCriteria = applyNameCriteria;
}
@Override
@@ -90,7 +93,8 @@ public class MooldePluginLmsAPITemplateFactory implements LmsAPITemplateFactory
this.asyncService,
moodleRestTemplateFactory,
this.cacheManager,
- this.environment);
+ this.environment,
+ this.applyNameCriteria);
final MoodlePluginCourseRestriction moodlePluginCourseRestriction = new MoodlePluginCourseRestriction(
this.jsonMapper,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
index d49e8c4d..b5f61c64 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
@@ -241,7 +241,9 @@ public class ExamSessionServiceImpl implements ExamSessionService {
flushCache(exam);
}
- log.info("Exam {} is not currently running", examId);
+ if (log.isDebugEnabled()) {
+ log.info("Exam {} is not currently running", examId);
+ }
return Result.ofError(new NoSuchElementException(
"No currently running exam found for id: " + examId));
@@ -265,8 +267,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
filterMap
.putIfAbsent(Exam.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
- .putIfAbsent(Exam.FILTER_ATTR_STATUS, ExamStatus.RUNNING.name())
- .putIfAbsent(Exam.FILTER_ATTR_HIDE_MISSING, Constants.TRUE_STRING);
+ .putIfAbsent(Exam.FILTER_ATTR_STATUS, ExamStatus.RUNNING.name());
return this.examDAO.allMatching(filterMap, predicate)
.map(col -> col.stream()
@@ -640,12 +641,14 @@ public class ExamSessionServiceImpl implements ExamSessionService {
final ClientConnectionDataInternal cc = this.examSessionCacheService.getClientConnection(token);
if (cc.clientConnection.status.duplicateCheckStatus) {
- final Long id = this.duplicateCheck.put(
- cc.clientConnection.userSessionId,
- cc.getConnectionId());
- if (id != null) {
- duplicates.add(id);
- duplicates.add(cc.getConnectionId());
+ if (cc.clientConnection.userSessionId != null) {
+ final Long id = this.duplicateCheck.put(
+ cc.clientConnection.userSessionId,
+ cc.getConnectionId());
+ if (id != null) {
+ duplicates.add(id);
+ duplicates.add(cc.getConnectionId());
+ }
}
}
return cc;
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 665e9400..033e7e5f 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
@@ -410,11 +410,14 @@ class ExamUpdateHandler {
.getLmsAPITemplate(lmsSetupId)
.getOrThrow();
+ final Exam exam = exams.get(quizId);
if (!lmsTemplate.getType().features.contains(Features.COURSE_RECOVERY)) {
+ if (exam.lmsAvailable == null || exam.isLmsAvailable()) {
+ this.examDAO.markLMSAvailability(quizId, false, updateId);
+ }
throw new UnsupportedOperationException("No Course Recovery");
}
- final Exam exam = exams.get(quizId);
final int attempts = Integer.parseInt(this.additionalAttributesDAO.getAdditionalAttribute(
EntityType.EXAM,
exam.id,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
index ab726f6c..d5a80ccd 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
@@ -329,7 +329,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
if (StringUtils.isNoneBlank(clientAddress) &&
StringUtils.isNotBlank(clientConnection.clientAddress) &&
!clientAddress.equals(clientConnection.clientAddress)) {
- log.error(
+ log.warn(
"ClientConnection integrity violation: client address mismatch: {}, {}",
clientAddress,
clientConnection.clientAddress);
@@ -337,7 +337,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
"ClientConnection integrity violation: client address mismatch");
}
} else if (!clientConnection.status.clientActiveStatus) {
- log.error("ClientConnection integrity violation: client connection is not in expected state: {}",
+ log.warn("ClientConnection integrity violation: client connection is not in expected state: {}",
clientConnection);
throw new IllegalArgumentException(
"ClientConnection integrity violation: client connection is not in expected state");
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java
index 89a1c31f..6c2decb6 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java
@@ -93,7 +93,7 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
this.clientEventDAO.getPendingNotificationByValue(clientConnection.id, notificationId)
.flatMap(notification -> this.clientEventDAO.confirmPendingNotification(notification.id))
.map(this::removeFromCache)
- .onError(error -> log.error("Failed to confirm pending notification: {}", event, error));
+ .getOrThrow();
} catch (final Exception e) {
log.error(
@@ -110,8 +110,7 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
return this.clientEventDAO.getPendingNotification(notificationId)
.map(notification -> this.confirmClientSide(notification, examId, connectionToken))
.flatMap(notification -> this.clientEventDAO.confirmPendingNotification(notificationId))
- .map(this::removeFromCache)
- .onError(error -> log.error("Failed to confirm pending notification: {}", notificationId, error));
+ .map(this::removeFromCache);
}
@Override
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java
index 891c4a9e..e0c469a8 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java
@@ -326,6 +326,11 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
cc.getExamId(),
cc.getConnectionToken());
+ if (proctoringRoom == null) {
+ log.warn("Assign SEB client to proctoring room failed for: {}", cc);
+ return;
+ }
+
if (log.isDebugEnabled()) {
log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
proctoringRoom.id,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java
index 4d0581d2..d8f02488 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java
@@ -149,6 +149,15 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
HttpStatus.BAD_REQUEST);
}
+ @ExceptionHandler(IllegalArgumentException.class)
+ public ResponseEntity