Merge remote-tracking branch 'origin/rel-1.5.1'
Conflicts: README.rst
This commit is contained in:
commit
ac880674d0
30 changed files with 179 additions and 77 deletions
|
@ -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
|
||||
------------------------------
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -18,7 +18,7 @@
|
|||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<sebserver-version>1.5.0</sebserver-version>
|
||||
<sebserver-version>1.5.1</sebserver-version>
|
||||
<build-version>${sebserver-version}</build-version>
|
||||
<revision>${sebserver-version}</revision>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<EntityKey> dependencies = report.results.stream()
|
||||
.filter(key -> !key.equals(entityKey))
|
||||
.collect(Collectors.toList());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -56,14 +56,14 @@ public final class UpdateErrorHandler implements Function<Exception, Boolean> {
|
|||
} 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();
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ public abstract class RestCall<T> {
|
|||
e,
|
||||
"NO RESPONSE AVAILABLE",
|
||||
String.valueOf(builder)));
|
||||
return Result.ofError(e);
|
||||
return Result.ofError(restCallError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,6 +216,10 @@ public abstract class RestCall<T> {
|
|||
} 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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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<AsyncQuizFetchBuffer> 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<AsyncQuizFetchBuffer> asyncBuffers) {
|
||||
final Collection<AsyncQuizFetchBuffer> 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;
|
||||
|
|
|
@ -63,7 +63,8 @@ public class MockCourseAccessAPI implements CourseAccessAPI {
|
|||
"quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "<p>Demo Quiz Mockup</p>",
|
||||
"2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP)", "<p>Demo Quiz Mockup</p>",
|
||||
"quiz2 äöüèÜÄÖ ?<", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP) äöüèÜÄÖ ?< ",
|
||||
"<p>Demo Quiz Mockup</p>",
|
||||
"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)", "<p>Demo Quiz Mockup</p>",
|
||||
|
|
|
@ -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<QuizData> 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<QuizData> 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<CourseData> 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<CourseData> 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<String, String> 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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -149,6 +149,15 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
|||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<Object> handleIllegalArgumentException(
|
||||
final IllegalArgumentException ex,
|
||||
final WebRequest request) {
|
||||
|
||||
log.warn("Illegal argument or state detected: {}\n send 400 Bad Request response", ex.getMessage());
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResourceNotFoundException.class)
|
||||
public ResponseEntity<Object> handleResourceNotFoundException(
|
||||
final ResourceNotFoundException ex,
|
||||
|
@ -182,7 +191,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
|||
final ExamNotRunningException ex,
|
||||
final WebRequest request) {
|
||||
|
||||
log.info("{}", ex.getMessage());
|
||||
log.debug("{}", ex.getMessage());
|
||||
return APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.createErrorResponse(ex.getMessage());
|
||||
}
|
||||
|
|
|
@ -425,7 +425,10 @@ public class ExamMonitoringController {
|
|||
notificationId,
|
||||
examId,
|
||||
connectionToken)
|
||||
.getOrThrow();
|
||||
.onError(error -> {
|
||||
log.error("Failed to confirm pending notification: {} for exam {}, cause: {}",
|
||||
notificationId, examId, error.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
|
|
@ -18,7 +18,7 @@ spring.datasource.hikari.leakDetectionThreshold=2000
|
|||
|
||||
sebserver.http.client.connect-timeout=15000
|
||||
sebserver.http.client.connection-request-timeout=10000
|
||||
sebserver.http.client.read-timeout=20000
|
||||
sebserver.http.client.read-timeout=30000
|
||||
sebserver.webservice.distributed.updateInterval=1000
|
||||
sebserver.webservice.distributed.connectionUpdate=2000
|
||||
sebserver.webservice.clean-db-on-startup=false
|
||||
|
@ -52,7 +52,7 @@ sebserver.webservice.api.pagination.maxPageSize=500
|
|||
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
||||
sebserver.webservice.lms.moodle.api.token.request.paths=
|
||||
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
|
||||
sebserver.webservice.cache.moodle.course.pageSize=10
|
||||
sebserver.webservice.cache.moodle.course.pageSize=250
|
||||
|
||||
springdoc.api-docs.enabled=true
|
||||
springdoc.swagger-ui.enabled=true
|
||||
|
|
|
@ -81,8 +81,10 @@ sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
|||
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
|
||||
sebserver.webservice.lms.moodle.prependShortCourseName=true
|
||||
sebserver.webservice.lms.moodle.fetch.cutoffdate.yearsBeforeNow=2
|
||||
sebserver.webservice.lms.moodle.fetch.applyNameCriteria=true
|
||||
sebserver.webservice.lms.olat.sendAdditionalAttributesWithRestriction=false
|
||||
sebserver.webservice.lms.address.alias=
|
||||
sebserver.webservice.lms.datafetch.validity.seconds=600
|
||||
|
||||
sebserver.webservice.proctoring.resetBroadcastOnLeav=true
|
||||
sebserver.webservice.proctoring.zoom.enableWaitingRoom=false
|
||||
|
|
|
@ -15,6 +15,12 @@ server.servlet.context-path=/
|
|||
# Tomcat
|
||||
server.tomcat.max-threads=2000
|
||||
server.tomcat.accept-count=300
|
||||
server.tomcat.socket.soKeepAlive=true
|
||||
server.tomcat.socket.performanceConnectionTime=1
|
||||
server.tomcat.socket.performanceLatency=2
|
||||
server.tomcat.socket.performanceBandwidth=0
|
||||
server.tomcat.keepAliveTimeout(3000);
|
||||
server.tomcat.maxKeepAliveRequests(3000);
|
||||
server.tomcat.uri-encoding=UTF-8
|
||||
|
||||
### encoding
|
||||
|
|
|
@ -448,6 +448,7 @@ sebserver.quizdiscovery.list.column.endtime.tooltip=The end time of the LMS exam
|
|||
sebserver.quizdiscovery.info.pleaseSelect=At first please select an LMS exam from the list
|
||||
sebserver.quizdiscovery.list.action.no.modify.privilege=No Access: A LMS exam from other institution cannot be imported.
|
||||
sebserver.quizdiscovery.list.fetchnote=<b>Note:</b> This list is not complete yet since the service is still fetching data from LMS.<br/> Use the reload button on the left or the search icon from the list for update.
|
||||
sebserver.quizdiscovery.list.fetchnote.tooltip=Click to reload the list and get all currently fetched results.
|
||||
|
||||
sebserver.quizdiscovery.action.list=LMS Exam Lookup
|
||||
sebserver.quizdiscovery.action.import=Import as Exam
|
||||
|
@ -674,14 +675,14 @@ sebserver.exam.configuration.form.title.new=Add exam configuration mapping
|
|||
sebserver.exam.configuration.form.title=Exam Configuration Mapping
|
||||
sebserver.exam.configuration.form.name=Exam Configuration
|
||||
sebserver.exam.configuration.form.name.tooltip=Please select an exam configuration to attach to the exam
|
||||
sebserver.exam.configuration.form.encryptSecret=Encryption Password
|
||||
sebserver.exam.configuration.form.encryptSecret.tooltip=Define an encryption password if the exam configuration should be encrypted by password
|
||||
sebserver.exam.configuration.form.encryptSecret=Settings Password
|
||||
sebserver.exam.configuration.form.encryptSecret.tooltip=Define an encryption password if the exam configuration settings should be encrypted by password
|
||||
sebserver.exam.configuration.form.description=Description
|
||||
sebserver.exam.configuration.form.description.tooltip=The description of the selected exam configuration
|
||||
sebserver.exam.configuration.form.status=Status
|
||||
sebserver.exam.configuration.form.status.tooltip=The current status of the selected exam configuration
|
||||
sebserver.exam.configuration.form.encryptSecret.confirm=Confirm Password
|
||||
sebserver.exam.configuration.form.encryptSecret.confirm.tooltip=Please confirm the encryption password if there is one
|
||||
sebserver.exam.configuration.form.encryptSecret.confirm.tooltip=Please confirm the settings password if there is one
|
||||
|
||||
sebserver.exam.indicator.list.actions=
|
||||
sebserver.exam.indicator.list.title=Indicators
|
||||
|
|
|
@ -36,12 +36,12 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
|||
sebAdminAccess,
|
||||
sebAdminAccess,
|
||||
"LmsSetupMock",
|
||||
"quiz2",
|
||||
"quiz2 äöüèÜÄÖ ?<",
|
||||
ExamType.MANAGED,
|
||||
"user5");
|
||||
|
||||
assertNotNull(exam);
|
||||
assertEquals("quiz2", exam.getExternalId());
|
||||
assertEquals("quiz2 äöüèÃÃà ?<", exam.getExternalId()); // Note cannot set right collation on h2
|
||||
assertEquals(ExamType.MANAGED, exam.getType());
|
||||
assertFalse(exam.getSupporter().isEmpty());
|
||||
|
||||
|
|
|
@ -324,7 +324,8 @@ public class MoodlePluginCourseAccessTest {
|
|||
asyncService,
|
||||
moodleMockupRestTemplateFactory,
|
||||
new NoOpCacheManager(),
|
||||
mockEnvironment);
|
||||
mockEnvironment,
|
||||
false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue