Merge remote-tracking branch 'origin/rel-1.5.2'
This commit is contained in:
		
						commit
						bf04341cf9
					
				
					 10 changed files with 100 additions and 18 deletions
				
			
		
							
								
								
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -18,7 +18,7 @@ | ||||||
|   <packaging>jar</packaging> |   <packaging>jar</packaging> | ||||||
| 
 | 
 | ||||||
|   <properties> |   <properties> | ||||||
|     <sebserver-version>1.5.1</sebserver-version> |     <sebserver-version>1.5.2</sebserver-version> | ||||||
|     <build-version>${sebserver-version}</build-version> |     <build-version>${sebserver-version}</build-version> | ||||||
|     <revision>${sebserver-version}</revision> |     <revision>${sebserver-version}</revision> | ||||||
|     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import org.eclipse.rap.rwt.RWT; | ||||||
| import org.eclipse.swt.SWT; | import org.eclipse.swt.SWT; | ||||||
| import org.eclipse.swt.layout.GridData; | import org.eclipse.swt.layout.GridData; | ||||||
| import org.eclipse.swt.widgets.Composite; | import org.eclipse.swt.widgets.Composite; | ||||||
|  | import org.eclipse.swt.widgets.Control; | ||||||
| import org.eclipse.swt.widgets.Label; | import org.eclipse.swt.widgets.Label; | ||||||
| import org.joda.time.DateTime; | import org.joda.time.DateTime; | ||||||
| import org.joda.time.DateTimeZone; | import org.joda.time.DateTimeZone; | ||||||
|  | @ -451,31 +452,28 @@ public class QuizLookupList implements TemplateComposer { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Composite warningPanel = null; |  | ||||||
| 
 |  | ||||||
|     private void handelPageReload( |     private void handelPageReload( | ||||||
|             final Composite notePanel, |             final Composite notePanel, | ||||||
|             final EntityTable<QuizData> table) { |             final EntityTable<QuizData> table) { | ||||||
| 
 | 
 | ||||||
|         if (table.isComplete()) { |         if (table.isComplete()) { | ||||||
|             PageService.clearComposite(notePanel); |             PageService.clearComposite(notePanel); | ||||||
|             if (this.warningPanel != null) { |  | ||||||
|                 this.warningPanel.dispose(); |  | ||||||
|             } |  | ||||||
|             this.warningPanel = null; |  | ||||||
|         } else { |         } 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( |             this.widgetFactory.imageButton( | ||||||
|                     ImageIcon.SWITCH, |                     ImageIcon.SWITCH, | ||||||
|                     this.warningPanel, |                     warningPanel, | ||||||
|                     TEXT_FETCH_NOTE_TOOLTIP, |                     TEXT_FETCH_NOTE_TOOLTIP, | ||||||
|                     event -> table.applyFilter()); |                     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.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); | ||||||
|             text.setText(this.pageService.getI18nSupport().getText(TEXT_FETCH_NOTE)); |             text.setText(this.pageService.getI18nSupport().getText(TEXT_FETCH_NOTE)); | ||||||
|             final GridData gridData = new GridData(SWT.LEFT, SWT.FILL, true, true); |             final GridData gridData = new GridData(SWT.LEFT, SWT.FILL, true, true); | ||||||
|  |  | ||||||
|  | @ -224,6 +224,9 @@ public class EntityTable<ROW extends ModelIdAware> { | ||||||
|         } |         } | ||||||
|         this.table.addListener(SWT.Selection, event -> { |         this.table.addListener(SWT.Selection, event -> { | ||||||
|             if (this.multiselection != null && event.item != null) { |             if (this.multiselection != null && event.item != null) { | ||||||
|  |                 if (event.item == null || event.item.isDisposed()) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|                 handleMultiSelection((TableItem) event.item); |                 handleMultiSelection((TableItem) event.item); | ||||||
|                 event.doit = false; |                 event.doit = false; | ||||||
|             } |             } | ||||||
|  | @ -527,6 +530,9 @@ public class EntityTable<ROW extends ModelIdAware> { | ||||||
| 
 | 
 | ||||||
|         // first remove all rows if there are some |         // first remove all rows if there are some | ||||||
|         this.table.removeAll(); |         this.table.removeAll(); | ||||||
|  |         if (this.multiselection != null) { | ||||||
|  |             this.multiselection.clear(); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         // get page data and create rows |         // get page data and create rows | ||||||
|         final Page<ROW> page = this.pageSupplier.newBuilder() |         final Page<ROW> page = this.pageSupplier.newBuilder() | ||||||
|  | @ -642,10 +648,19 @@ public class EntityTable<ROW extends ModelIdAware> { | ||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |     @SuppressWarnings("unchecked") | ||||||
|     private ROW getRowData(final TableItem item) { |     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); |         return (ROW) item.getData(TABLE_ROW_DATA); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private EntityKey getEntityKey(final TableItem item) { |     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); |         final ROW rowData = getRowData(item); | ||||||
|         if (rowData instanceof Entity) { |         if (rowData instanceof Entity) { | ||||||
|             return ((Entity) rowData).getEntityKey(); |             return ((Entity) rowData).getEntityKey(); | ||||||
|  |  | ||||||
|  | @ -98,7 +98,7 @@ public class PasswordInput extends Composite { | ||||||
|         final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); |         final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); | ||||||
|         passwordInput.setLayoutData(gridData); |         passwordInput.setLayoutData(gridData); | ||||||
|         passwordInput.setText(value != null |         passwordInput.setText(value != null | ||||||
|                 ? Utils.escapeHTML_XML_EcmaScript(value) |                 ? value | ||||||
|                 : StringUtils.EMPTY); |                 : StringUtils.EMPTY); | ||||||
|         if (!buildPassword) { |         if (!buildPassword) { | ||||||
|             passwordInput.setEditable(false); |             passwordInput.setEditable(false); | ||||||
|  |  | ||||||
|  | @ -536,6 +536,10 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme | ||||||
|                         lmsSetup); |                         lmsSetup); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             final Set<String> qIdSet = internalIds.stream() | ||||||
|  |                     .map(MoodleUtils::getQuizId) | ||||||
|  |                     .collect(Collectors.toSet()); | ||||||
|  | 
 | ||||||
|             return getCoursesForIds(restTemplate, moodleCourseIds) |             return getCoursesForIds(restTemplate, moodleCourseIds) | ||||||
|                     .stream() |                     .stream() | ||||||
|                     .filter(courseData -> !courseData.quizzes.isEmpty()) |                     .filter(courseData -> !courseData.quizzes.isEmpty()) | ||||||
|  | @ -545,7 +549,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme | ||||||
|                             urlPrefix, |                             urlPrefix, | ||||||
|                             this.prependShortCourseName) |                             this.prependShortCourseName) | ||||||
|                             .stream() |                             .stream() | ||||||
|                             .filter(q -> internalIds.contains(q.id))) |                             .filter(q -> qIdSet.contains(MoodleUtils.getQuizId(q.id)))) | ||||||
|                     .collect(Collectors.toList()); |                     .collect(Collectors.toList()); | ||||||
| 
 | 
 | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|  |  | ||||||
|  | @ -163,6 +163,23 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm | ||||||
|                     "lmsSetup:lmsClientsecret:notNull")); |                     "lmsSetup:lmsClientsecret:notNull")); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         final Result<OlatLmsRestTemplate> 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()) { |         if (!missingAttrs.isEmpty()) { | ||||||
|             return LmsSetupTestResult.ofMissingAttributes(LmsType.OPEN_OLAT, missingAttrs); |             return LmsSetupTestResult.ofMissingAttributes(LmsType.OPEN_OLAT, missingAttrs); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -32,6 +32,12 @@ public class OlatLmsRestTemplate extends RestTemplate { | ||||||
|     private String token; |     private String token; | ||||||
|     private ClientCredentialsResourceDetails details; |     private ClientCredentialsResourceDetails details; | ||||||
| 
 | 
 | ||||||
|  |     public void testAuthentication() { | ||||||
|  |         if (this.token == null) { | ||||||
|  |             authenticate(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public OlatLmsRestTemplate(final ClientCredentialsResourceDetails details) { |     public OlatLmsRestTemplate(final ClientCredentialsResourceDetails details) { | ||||||
|         super(); |         super(); | ||||||
|         this.details = details; |         this.details = details; | ||||||
|  | @ -59,6 +65,7 @@ public class OlatLmsRestTemplate extends RestTemplate { | ||||||
| 
 | 
 | ||||||
|                         return execution.execute(request, body); |                         return execution.execute(request, body); | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|                     // otherwise, add the X-OLAT-TOKEN |                     // otherwise, add the X-OLAT-TOKEN | ||||||
|                     request.getHeaders().set("accept", "application/json"); |                     request.getHeaders().set("accept", "application/json"); | ||||||
|                     request.getHeaders().set("X-OLAT-TOKEN", OlatLmsRestTemplate.this.token); |                     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()); |                             log.debug("OLAT [retry API call]: URL {}", request.getURI()); | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|  |                         response.close(); | ||||||
|                         response = execution.execute(request, body); |                         response = execution.execute(request, body); | ||||||
| 
 | 
 | ||||||
|                         if (log.isDebugEnabled()) { |                         if (log.isDebugEnabled()) { | ||||||
|  | @ -97,7 +105,7 @@ public class OlatLmsRestTemplate extends RestTemplate { | ||||||
| 
 | 
 | ||||||
|                 } catch (final Exception e) { |                 } catch (final Exception e) { | ||||||
|                     // TODO find a way to better deal with Olat temporary unavailability |                     // TODO find a way to better deal with Olat temporary unavailability | ||||||
|                     log.error("Unexpected error: ", e); |                     log.error("Unexpected error: {}", e.getMessage()); | ||||||
|                     throw e; |                     throw e; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ import java.util.Collections; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
|  | import java.util.Optional; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| 
 | 
 | ||||||
| import org.joda.time.DateTime; | 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.Exam.ExamStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; | 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.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.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | 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.LmsAPIService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; | 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.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.ExamFinishedEvent; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamResetEvent; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamResetEvent; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamStartedEvent; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamStartedEvent; | ||||||
|  | @ -138,7 +141,12 @@ class ExamUpdateHandler { | ||||||
|                     .forEach(quiz -> { |                     .forEach(quiz -> { | ||||||
| 
 | 
 | ||||||
|                         try { |                         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)) { |                             if (hasChanges(exam, quiz)) { | ||||||
| 
 | 
 | ||||||
|  | @ -356,7 +364,8 @@ class ExamUpdateHandler { | ||||||
|                 !Objects.equals(exam.startTime, quizData.startTime) || |                 !Objects.equals(exam.startTime, quizData.startTime) || | ||||||
|                 !Objects.equals(exam.endTime, quizData.endTime) || |                 !Objects.equals(exam.endTime, quizData.endTime) || | ||||||
|                 !Utils.isEqualsWithEmptyCheckTruncated(exam.getDescription(), quizData.description) || |                 !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)) { |             if (!Utils.isEqualsWithEmptyCheck(exam.name, quizData.name)) { | ||||||
|                 log.info("Update name difference from LMS. Exam: {}, QuizData: {}", 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(), |                         exam.getStartURL(), | ||||||
|                         quizData.startURL); |                         quizData.startURL); | ||||||
|             } |             } | ||||||
|  |             if (!Objects.equals(exam.externalId, quizData.id)) { | ||||||
|  |                 log.info("Update quizId difference from LMS. Exam:{}, QuizData: {}", | ||||||
|  |                         exam.externalId, | ||||||
|  |                         quizData.id); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  | @ -479,4 +493,29 @@ class ExamUpdateHandler { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private Exam getExamForQuizWithMoodleSpecialCase(final Map<String, Exam> 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<String> 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; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ sebserver.webservice.clean-db-on-startup=false | ||||||
| 
 | 
 | ||||||
| # webservice configuration | # webservice configuration | ||||||
| sebserver.init.adminaccount.gen-on-init=false | sebserver.init.adminaccount.gen-on-init=false | ||||||
| sebserver.webservice.distributed=true | sebserver.webservice.distributed=false | ||||||
| #sebserver.webservice.master.delay.threshold=10000 | #sebserver.webservice.master.delay.threshold=10000 | ||||||
| sebserver.webservice.http.external.scheme=http | sebserver.webservice.http.external.scheme=http | ||||||
| sebserver.webservice.http.external.servername=localhost | sebserver.webservice.http.external.servername=localhost | ||||||
|  |  | ||||||
|  | @ -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.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.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.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.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.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) | sebserver.form.validation.fieldError.invalidIP=Invalid IP v4. Please enter a valid IP-address (v4) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue