From 8fe93b41c3ea4ce34e65bfd8c85e8eaebbf9a6d9 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Thu, 25 Sep 2014 17:39:46 +0200 Subject: [PATCH] OO-1209: import more metadatas, sanity check for numbers --- .../ims/qti/qpool/QTIImportProcessor.java | 54 ++++- .../ims/qti/qpool/QTIMetadataConverter.java | 25 ++- .../qti/qpool/QTIQPoolServiceProvider.java | 9 +- .../CSVToQuestionConverter.java | 203 +++++++++++++++--- .../qti/questionimport/ItemAndMetadata.java | 83 +++++++ .../ims/qti/questionimport/ItemsPackage.java | 19 +- .../OverviewQuestionController.java | 1 + .../modules/qpool/manager/QLicenseDAO.java | 15 ++ .../ims/qti/qpool/QTIExportProcessorTest.java | 5 +- .../ims/qti/qpool/QTIImportProcessorTest.java | 23 +- .../CSVToQuestionConverterTest.java | 40 ++++ .../question_import_fib_en_metadata.txt | 22 ++ 12 files changed, 439 insertions(+), 60 deletions(-) create mode 100644 src/test/java/org/olat/ims/qti/questionimport/question_import_fib_en_metadata.txt diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java index 9e990d1c1aa..4f9e934a59a 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java @@ -74,11 +74,13 @@ import org.olat.modules.qpool.QuestionType; import org.olat.modules.qpool.TaxonomyLevel; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; +import org.olat.modules.qpool.manager.QLicenseDAO; import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QuestionItemDAO; import org.olat.modules.qpool.manager.TaxonomyLevelDAO; import org.olat.modules.qpool.model.QEducationalContext; import org.olat.modules.qpool.model.QItemType; +import org.olat.modules.qpool.model.QLicense; import org.olat.modules.qpool.model.QuestionItemImpl; import org.xml.sax.Attributes; import org.xml.sax.InputSource; @@ -104,6 +106,7 @@ class QTIImportProcessor { private final File importedFile; private final DB dbInstance; + private final QLicenseDAO qLicenseDao; private final QItemTypeDAO qItemTypeDao; private final QPoolFileStorage qpoolFileStorage; private final QuestionItemDAO questionItemDao; @@ -112,19 +115,22 @@ class QTIImportProcessor { public QTIImportProcessor(Identity owner, Locale defaultLocale, QuestionItemDAO questionItemDao, QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao, - TaxonomyLevelDAO taxonomyLevelDao, QPoolFileStorage qpoolFileStorage, DB dbInstance) { + TaxonomyLevelDAO taxonomyLevelDao, QLicenseDAO qLicenseDao, QPoolFileStorage qpoolFileStorage, + DB dbInstance) { this(owner, defaultLocale, null, null, questionItemDao, qItemTypeDao, qEduContextDao, - taxonomyLevelDao, qpoolFileStorage, dbInstance); + taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); } public QTIImportProcessor(Identity owner, Locale defaultLocale, String importedFilename, File importedFile, QuestionItemDAO questionItemDao, QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao, - TaxonomyLevelDAO taxonomyLevelDao, QPoolFileStorage qpoolFileStorage, DB dbInstance) { + TaxonomyLevelDAO taxonomyLevelDao, QLicenseDAO qLicenseDao, QPoolFileStorage qpoolFileStorage, + DB dbInstance) { this.owner = owner; this.dbInstance = dbInstance; this.defaultLocale = defaultLocale; this.importedFilename = importedFilename; this.importedFile = importedFile; + this.qLicenseDao = qLicenseDao; this.qItemTypeDao = qItemTypeDao; this.questionItemDao = questionItemDao; this.qEduContextDao = qEduContextDao; @@ -285,10 +291,48 @@ class QTIImportProcessor { String taxonomyPath = metadata.getTaxonomyPath(); if(StringHelper.containsNonWhitespace(taxonomyPath)) { - QTIMetadataConverter converter = new QTIMetadataConverter(null, taxonomyLevelDao, null); + QTIMetadataConverter converter = new QTIMetadataConverter(qItemTypeDao, qLicenseDao, taxonomyLevelDao, qEduContextDao); TaxonomyLevel taxonomyLevel = converter.toTaxonomy(taxonomyPath); poolItem.setTaxonomyLevel(taxonomyLevel); } + + String level = metadata.getLevel(); + if(StringHelper.containsNonWhitespace(level)) { + QTIMetadataConverter converter = new QTIMetadataConverter(qItemTypeDao, qLicenseDao, taxonomyLevelDao, qEduContextDao); + QEducationalContext educationalContext = converter.toEducationalContext(level); + poolItem.setEducationalContext(educationalContext); + } + + String time = metadata.getTypicalLearningTime(); + if(StringHelper.containsNonWhitespace(time)) { + poolItem.setEducationalLearningTime(time); + } + + String editor = metadata.getEditor(); + if(StringHelper.containsNonWhitespace(editor)) { + poolItem.setEditor(editor); + } + + String editorVersion = metadata.getEditorVersion(); + if(StringHelper.containsNonWhitespace(editorVersion)) { + poolItem.setEditorVersion(editorVersion); + } + + int numOfAnswerAlternatives = metadata.getNumOfAnswerAlternatives(); + if(numOfAnswerAlternatives > 0) { + poolItem.setNumOfAnswerAlternatives(numOfAnswerAlternatives); + } + + poolItem.setDifficulty(metadata.getDifficulty()); + poolItem.setDifferentiation(metadata.getDifferentiation()); + poolItem.setStdevDifficulty(metadata.getStdevDifficulty()); + + String license = metadata.getLicense(); + if(StringHelper.containsNonWhitespace(license)) { + QTIMetadataConverter converter = new QTIMetadataConverter(qItemTypeDao, qLicenseDao, taxonomyLevelDao, qEduContextDao); + QLicense qLicense = converter.toLicense(license); + poolItem.setLicense(qLicense); + } } private void processItemMetadata(QuestionItemImpl poolItem, Element itemEl) { @@ -600,7 +644,7 @@ class QTIImportProcessor { SAXReader reader = new SAXReader(); Document document = reader.read(metadataIn); Element rootElement = document.getRootElement(); - QTIMetadataConverter enricher = new QTIMetadataConverter(rootElement, qItemTypeDao, taxonomyLevelDao, qEduContextDao); + QTIMetadataConverter enricher = new QTIMetadataConverter(rootElement, qItemTypeDao, qLicenseDao, taxonomyLevelDao, qEduContextDao); enricher.toQuestion(item); } return true; diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIMetadataConverter.java b/src/main/java/org/olat/ims/qti/qpool/QTIMetadataConverter.java index bc37da9151b..1f6e5d6a513 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIMetadataConverter.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIMetadataConverter.java @@ -24,12 +24,14 @@ import java.util.ArrayList; import java.util.List; import org.dom4j.Element; +import org.jgroups.util.UUID; import org.olat.core.util.StringHelper; import org.olat.modules.qpool.QuestionItemFull; import org.olat.modules.qpool.QuestionStatus; import org.olat.modules.qpool.TaxonomyLevel; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; +import org.olat.modules.qpool.manager.QLicenseDAO; import org.olat.modules.qpool.manager.TaxonomyLevelDAO; import org.olat.modules.qpool.model.QEducationalContext; import org.olat.modules.qpool.model.QItemType; @@ -48,7 +50,8 @@ import org.olat.modules.qpool.model.QuestionItemImpl; class QTIMetadataConverter { private Element qtimetadata; - + + private QLicenseDAO licenseDao; private QItemTypeDAO itemTypeDao; private TaxonomyLevelDAO taxonomyLevelDao; private QEducationalContextDAO educationalContextDao; @@ -57,16 +60,18 @@ class QTIMetadataConverter { this.qtimetadata = qtimetadata; } - QTIMetadataConverter(Element qtimetadata, QItemTypeDAO itemTypeDao, + QTIMetadataConverter(Element qtimetadata, QItemTypeDAO itemTypeDao, QLicenseDAO licenseDao, TaxonomyLevelDAO taxonomyLevelDao, QEducationalContextDAO educationalContextDao) { this.qtimetadata = qtimetadata; + this.licenseDao = licenseDao; this.itemTypeDao = itemTypeDao; this.taxonomyLevelDao = taxonomyLevelDao; this.educationalContextDao = educationalContextDao; } - QTIMetadataConverter(QItemTypeDAO itemTypeDao, + QTIMetadataConverter(QItemTypeDAO itemTypeDao, QLicenseDAO licenseDao, TaxonomyLevelDAO taxonomyLevelDao, QEducationalContextDAO educationalContextDao) { + this.licenseDao = licenseDao; this.itemTypeDao = itemTypeDao; this.taxonomyLevelDao = taxonomyLevelDao; this.educationalContextDao = educationalContextDao; @@ -80,8 +85,16 @@ class QTIMetadataConverter { return type; } - private QLicense toLicense(String str) { - return null; + public QLicense toLicense(String license) { + QLicense qLicense = null; + if(StringHelper.containsNonWhitespace(license)) { + qLicense = licenseDao.searchLicense(license); + if(qLicense == null) { + String key = "perso-" + UUID.randomUUID().toString(); + qLicense = licenseDao.create(key, license, false); + } + } + return qLicense; } public TaxonomyLevel toTaxonomy(String str) { @@ -106,7 +119,7 @@ class QTIMetadataConverter { return lowerLevel; } - private QEducationalContext toEducationalContext(String txt) { + public QEducationalContext toEducationalContext(String txt) { QEducationalContext context = educationalContextDao.loadByLevel(txt); if(context == null) { context = educationalContextDao.create(txt, true); diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java b/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java index ecfc6155b1a..88958c53c6a 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java @@ -71,6 +71,7 @@ import org.olat.modules.qpool.QuestionItemFull; import org.olat.modules.qpool.QuestionItemShort; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; +import org.olat.modules.qpool.manager.QLicenseDAO; import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QuestionItemDAO; import org.olat.modules.qpool.manager.TaxonomyLevelDAO; @@ -101,6 +102,8 @@ public class QTIQPoolServiceProvider implements QPoolSPI { @Autowired private QPoolFileStorage qpoolFileStorage; @Autowired + private QLicenseDAO qLicenseDao; + @Autowired private QItemTypeDAO qItemTypeDao; @Autowired private QuestionItemDAO questionItemDao; @@ -190,7 +193,7 @@ public class QTIQPoolServiceProvider implements QPoolSPI { @Override public List<QuestionItem> importItems(Identity owner, Locale defaultLocale, String filename, File file) { QTIImportProcessor processor = new QTIImportProcessor(owner, defaultLocale, filename, file, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); return processor.process(); } @@ -220,7 +223,7 @@ public class QTIQPoolServiceProvider implements QPoolSPI { item.setTitle(title); QTIImportProcessor processor = new QTIImportProcessor(owner, defaultLocale, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); Document doc = QTIEditHelper.itemToXml(item); Element itemEl = (Element)doc.selectSingleNode("questestinterop/item"); @@ -234,7 +237,7 @@ public class QTIQPoolServiceProvider implements QPoolSPI { public void importBeecomItem(Identity owner, ItemAndMetadata itemAndMetadata, VFSContainer sourceDir, Locale defaultLocale) { QTIImportProcessor processor = new QTIImportProcessor(owner, defaultLocale, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); String editor = null; String editorVersion = null; diff --git a/src/main/java/org/olat/ims/qti/questionimport/CSVToQuestionConverter.java b/src/main/java/org/olat/ims/qti/questionimport/CSVToQuestionConverter.java index 01e6105a21e..a2948dc3436 100644 --- a/src/main/java/org/olat/ims/qti/questionimport/CSVToQuestionConverter.java +++ b/src/main/java/org/olat/ims/qti/questionimport/CSVToQuestionConverter.java @@ -19,12 +19,14 @@ */ package org.olat.ims.qti.questionimport; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import org.olat.core.gui.translator.Translator; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; import org.olat.core.util.filter.Filter; import org.olat.core.util.filter.FilterFactory; import org.olat.ims.qti.editor.QTIEditHelper; @@ -105,82 +107,217 @@ public class CSVToQuestionConverter { case "keywords": processKeywords(parts); break; case "abdeckung": case "coverage": processCoverage(parts); break; - case "level": break; + case "level": processLevel(parts); break; case "sprache": case "language": processLanguage(parts); break; + case "durchschnittliche bearbeitungszeit": + case "typical learning time": processTypicalLearningTime(parts); break; + case "itemschwierigkeit": + case "difficulty index": processDifficultyIndex(parts); break; + case "standardabweichung itemschwierigkeit": + case "standard deviation": processStandardDeviation(parts); break; + case "trennsch\u00E4rfe": + case "discrimination index": processDiscriminationIndex(parts); break; + case "anzahl distraktoren": + case "distractors": processDistractors(parts); break; + case "editor": processEditor(parts); break; + case "editor version": processEditorVersion(parts); break; + case "lizenz": + case "license": processLicense(parts); break; default: processChoice(parts); } } + + private void processLevel(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String level = parts[1]; + if(StringHelper.containsNonWhitespace(level)) { + currentItem.setLevel(level.trim()); + } + } - private void processType(String[] parts) { - if(currentItem != null) { - items.add(currentItem); + private void processTypicalLearningTime(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String time = parts[1]; + if(StringHelper.containsNonWhitespace(time)) { + currentItem.setTypicalLearningTime(time.trim()); + } + } + + private void processLicense(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String license = parts[1]; + if(StringHelper.containsNonWhitespace(license)) { + currentItem.setLicense(license.trim()); + } + } + + private void processEditor(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String editor = parts[1]; + if(StringHelper.containsNonWhitespace(editor)) { + currentItem.setEditor(editor.trim()); } + } + + private void processEditorVersion(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String editorVersion = parts[1]; + if(StringHelper.containsNonWhitespace(editorVersion)) { + currentItem.setEditorVersion(editorVersion.trim()); + } + } + + private void processDistractors(String[] parts) { + if(currentItem == null || parts.length < 2) return; - String type = parts[1].toLowerCase(); - switch(type) { - case "fib": { - currentItem = new ItemAndMetadata(QTIEditHelper.createFIBItem(translator)); - ((FIBQuestion)currentItem.getItem().getQuestion()).getResponses().clear(); - break; + String distractors = parts[1]; + if(StringHelper.containsNonWhitespace(distractors)) { + try { + currentItem.setNumOfAnswerAlternatives(Integer.parseInt(distractors.trim())); + } catch (NumberFormatException e) { + log.warn("", e); } - case "mc": { - currentItem = new ItemAndMetadata(QTIEditHelper.createMCItem(translator)); - ((ChoiceQuestion)currentItem.getItem().getQuestion()).getResponses().clear(); - break; + } + } + + private void processDiscriminationIndex(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String discriminationIndex = parts[1]; + if(StringHelper.containsNonWhitespace(discriminationIndex)) { + try { + currentItem.setDifferentiation(new BigDecimal(discriminationIndex.trim())); + } catch (Exception e) { + log.warn("", e); } - case "sc": { - currentItem = new ItemAndMetadata(QTIEditHelper.createSCItem(translator)); - ((ChoiceQuestion)currentItem.getItem().getQuestion()).getResponses().clear(); - break; + } + } + + private void processDifficultyIndex(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String difficulty = parts[1]; + if(StringHelper.containsNonWhitespace(difficulty)) { + try { + BigDecimal dif = new BigDecimal(difficulty.trim()); + if(dif.doubleValue() >= 0.0d && dif.doubleValue() <= 1.0d) { + currentItem.setDifficulty(dif); + } else { + currentItem.setHasError(true); + } + } catch (Exception e) { + log.warn("", e); } - default: { - log.warn("Question type not supported: " + type); - currentItem = null; + } + } + + private void processStandardDeviation(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String stddev = parts[1]; + if(StringHelper.containsNonWhitespace(stddev)) { + try { + BigDecimal dev = new BigDecimal(stddev.trim()); + if(dev.doubleValue() >= 0.0d && dev.doubleValue() <= 1.0d) { + currentItem.setStdevDifficulty(dev); + } else { + currentItem.setHasError(true); + } + } catch (Exception e) { + log.warn("", e); + } + } + } + + private void processType(String[] parts) { + if(currentItem != null) { + items.add(currentItem); + } + + if(parts.length > 1) { + String type = parts[1].toLowerCase(); + switch(type) { + case "fib": { + currentItem = new ItemAndMetadata(QTIEditHelper.createFIBItem(translator)); + ((FIBQuestion)currentItem.getItem().getQuestion()).getResponses().clear(); + break; + } + case "mc": { + currentItem = new ItemAndMetadata(QTIEditHelper.createMCItem(translator)); + ((ChoiceQuestion)currentItem.getItem().getQuestion()).getResponses().clear(); + break; + } + case "sc": { + currentItem = new ItemAndMetadata(QTIEditHelper.createSCItem(translator)); + ((ChoiceQuestion)currentItem.getItem().getQuestion()).getResponses().clear(); + break; + } + default: { + log.warn("Question type not supported: " + type); + currentItem = null; + } } } } private void processCoverage(String[] parts) { - if(currentItem == null) return; + if(currentItem == null || parts.length < 2) return; String coverage = parts[1]; - currentItem.setCoverage(coverage); + if(StringHelper.containsNonWhitespace(coverage)) { + currentItem.setCoverage(coverage); + } } private void processKeywords(String[] parts) { - if(currentItem == null) return; + if(currentItem == null || parts.length < 2) return; String keywords = parts[1]; - currentItem.setKeywords(keywords); + if(StringHelper.containsNonWhitespace(keywords)) { + currentItem.setKeywords(keywords); + } } private void processTaxonomyPath(String[] parts) { - if(currentItem == null) return; + if(currentItem == null || parts.length < 2) return; String taxonomyPath = parts[1]; - currentItem.setTaxonomyPath(taxonomyPath); + if(StringHelper.containsNonWhitespace(taxonomyPath)) { + currentItem.setTaxonomyPath(taxonomyPath); + } } private void processLanguage(String[] parts) { - if(currentItem == null) return; + if(currentItem == null || parts.length < 2) return; String language = parts[1]; - currentItem.setLanguage(language); + if(StringHelper.containsNonWhitespace(language)) { + currentItem.setLanguage(language); + } } private void processTitle(String[] parts) { - if(currentItem == null) return; + if(currentItem == null || parts.length < 2) return; String title = parts[1]; - currentItem.setTitle(title); + if(StringHelper.containsNonWhitespace(title)) { + currentItem.setTitle(title); + } } private void processDescription(String[] parts) { - if(currentItem == null) return; + if(currentItem == null || parts.length < 2) return; String description = parts[1]; - currentItem.setDescription(description); + if(StringHelper.containsNonWhitespace(description)) { + currentItem.setDescription(description); + } } private void processQuestion(String[] parts) { diff --git a/src/main/java/org/olat/ims/qti/questionimport/ItemAndMetadata.java b/src/main/java/org/olat/ims/qti/questionimport/ItemAndMetadata.java index 132232bc2b1..a4877fe420f 100644 --- a/src/main/java/org/olat/ims/qti/questionimport/ItemAndMetadata.java +++ b/src/main/java/org/olat/ims/qti/questionimport/ItemAndMetadata.java @@ -19,6 +19,8 @@ */ package org.olat.ims.qti.questionimport; +import java.math.BigDecimal; + import org.olat.ims.qti.editor.beecom.objects.Item; /** @@ -35,6 +37,15 @@ public class ItemAndMetadata { private String taxonomyPath; private String keywords; private String coverage; + private String level; + private String typicalLearningTime; + private String license; + private String editor; + private String editorVersion; + private int numOfAnswerAlternatives; + private BigDecimal difficulty; + private BigDecimal differentiation; + private BigDecimal stdevDifficulty; private boolean hasError; @@ -46,6 +57,78 @@ public class ItemAndMetadata { return item; } + public BigDecimal getDifficulty() { + return difficulty; + } + + public void setDifficulty(BigDecimal difficulty) { + this.difficulty = difficulty; + } + + public BigDecimal getStdevDifficulty() { + return stdevDifficulty; + } + + public void setStdevDifficulty(BigDecimal stdevDifficulty) { + this.stdevDifficulty = stdevDifficulty; + } + + public BigDecimal getDifferentiation() { + return differentiation; + } + + public void setDifferentiation(BigDecimal differentiation) { + this.differentiation = differentiation; + } + + public int getNumOfAnswerAlternatives() { + return numOfAnswerAlternatives; + } + + public void setNumOfAnswerAlternatives(int numOfAnswerAlternatives) { + this.numOfAnswerAlternatives = numOfAnswerAlternatives; + } + + public String getEditor() { + return editor; + } + + public void setEditor(String editor) { + this.editor = editor; + } + + public String getEditorVersion() { + return editorVersion; + } + + public void setEditorVersion(String editorVersion) { + this.editorVersion = editorVersion; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + public String getTypicalLearningTime() { + return typicalLearningTime; + } + + public void setTypicalLearningTime(String typicalLearningTime) { + this.typicalLearningTime = typicalLearningTime; + } + public String getCoverage() { return coverage; } diff --git a/src/main/java/org/olat/ims/qti/questionimport/ItemsPackage.java b/src/main/java/org/olat/ims/qti/questionimport/ItemsPackage.java index b5e474c8950..9fdbab5e8f3 100644 --- a/src/main/java/org/olat/ims/qti/questionimport/ItemsPackage.java +++ b/src/main/java/org/olat/ims/qti/questionimport/ItemsPackage.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti.questionimport; +import java.lang.reflect.Method; import java.util.List; /** @@ -39,6 +40,20 @@ public class ItemsPackage { this.items = items; } - - + public boolean hasField(String field) { + boolean found = false; + try { + Method method = ItemAndMetadata.class.getDeclaredMethod(field); + for(ItemAndMetadata item:items) { + Object value = method.invoke(item); + if(value != null) { + found = true; + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return found; + } } diff --git a/src/main/java/org/olat/ims/qti/questionimport/OverviewQuestionController.java b/src/main/java/org/olat/ims/qti/questionimport/OverviewQuestionController.java index e7085823746..9f523c57349 100644 --- a/src/main/java/org/olat/ims/qti/questionimport/OverviewQuestionController.java +++ b/src/main/java/org/olat/ims/qti/questionimport/OverviewQuestionController.java @@ -69,6 +69,7 @@ public class OverviewQuestionController extends StepFormBasicController { columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.type.i18n(), Cols.type.ordinal())); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.title.i18n(), Cols.title.ordinal())); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.points.i18n(), Cols.points.ordinal())); + ItemsTableDataModel model = new ItemsTableDataModel(importedItems.getItems(), columnsModel); uifactory.addTableElement(getWindowControl(), "overviewTable", model, formLayout); } diff --git a/src/main/java/org/olat/modules/qpool/manager/QLicenseDAO.java b/src/main/java/org/olat/modules/qpool/manager/QLicenseDAO.java index 5f76604c5cc..de89752a2c6 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QLicenseDAO.java +++ b/src/main/java/org/olat/modules/qpool/manager/QLicenseDAO.java @@ -99,6 +99,21 @@ public class QLicenseDAO implements ApplicationListener<ContextRefreshedEvent> { return licenses.get(0); } + public QLicense searchLicense(String license) { + StringBuilder sb = new StringBuilder(); + sb.append("select license from qlicense license") + .append(" where license.licenseKey=:license or license.licenseText=:license"); + + List<QLicense> licenses = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), QLicense.class) + .setParameter("license", license) + .getResultList(); + if(licenses.isEmpty()) { + return null; + } + return licenses.get(0); + } + public boolean delete(QLicense license) { QLicense reloadLicense = loadById(license.getKey()); diff --git a/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java b/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java index b49f5c16fef..7b2414e0e3b 100644 --- a/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java +++ b/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java @@ -42,6 +42,7 @@ import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionItemFull; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; +import org.olat.modules.qpool.manager.QLicenseDAO; import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QuestionItemDAO; import org.olat.modules.qpool.manager.TaxonomyLevelDAO; @@ -66,6 +67,8 @@ public class QTIExportProcessorTest extends OlatTestCase { @Autowired private QItemTypeDAO qItemTypeDao; @Autowired + private QLicenseDAO qLicenseDao; + @Autowired private QuestionItemDAO questionItemDao; @Autowired private TaxonomyLevelDAO taxonomyLevelDao; @@ -87,7 +90,7 @@ public class QTIExportProcessorTest extends OlatTestCase { Assert.assertNotNull(itemUrl); File itemFile = new File(itemUrl.toURI()); QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); dbInstance.commitAndCloseSession(); diff --git a/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java b/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java index a31e8c28f83..7bb58d0ffa7 100644 --- a/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java +++ b/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java @@ -52,6 +52,7 @@ import org.olat.modules.qpool.QuestionStatus; import org.olat.modules.qpool.QuestionType; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; +import org.olat.modules.qpool.manager.QLicenseDAO; import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QuestionItemDAO; import org.olat.modules.qpool.manager.TaxonomyLevelDAO; @@ -79,6 +80,8 @@ public class QTIImportProcessorTest extends OlatTestCase { @Autowired private QItemTypeDAO qItemTypeDao; @Autowired + private QLicenseDAO qLicenseDao; + @Autowired private QuestionItemDAO questionItemDao; @Autowired private TaxonomyLevelDAO taxonomyLevelDao; @@ -105,7 +108,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao,qpoolFileStorage, dbInstance); List<DocInfos> docInfoList = proc.getDocInfos(); Assert.assertNotNull(docInfoList); Assert.assertEquals(1, docInfoList.size()); @@ -160,7 +163,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); Assert.assertEquals(1, items.size()); @@ -194,7 +197,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, testFile.getName(), testFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<DocInfos> docInfoList = proc.getDocInfos(); Assert.assertNotNull(docInfoList); Assert.assertEquals(1, docInfoList.size()); @@ -219,7 +222,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); Assert.assertEquals(4, items.size()); @@ -282,7 +285,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); Assert.assertEquals(2, items.size()); @@ -329,7 +332,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); Assert.assertEquals(3, items.size()); @@ -383,7 +386,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); Assert.assertEquals(2, items.size()); @@ -428,7 +431,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); Assert.assertEquals(1, items.size()); @@ -453,7 +456,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); Assert.assertEquals(1, items.size()); @@ -496,7 +499,7 @@ public class QTIImportProcessorTest extends OlatTestCase { //get the document informations QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qpoolFileStorage, dbInstance); + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); List<QuestionItem> items = proc.process(); Assert.assertNotNull(items); diff --git a/src/test/java/org/olat/ims/qti/questionimport/CSVToQuestionConverterTest.java b/src/test/java/org/olat/ims/qti/questionimport/CSVToQuestionConverterTest.java index 9a09213daa9..68ddbe2d438 100644 --- a/src/test/java/org/olat/ims/qti/questionimport/CSVToQuestionConverterTest.java +++ b/src/test/java/org/olat/ims/qti/questionimport/CSVToQuestionConverterTest.java @@ -153,4 +153,44 @@ public class CSVToQuestionConverterTest { Assert.assertEquals(2, ((FIBResponse)responses.get(3)).getMaxLength()); Assert.assertEquals(2, ((FIBResponse)responses.get(5)).getMaxLength()); } + + @Test + public void importFillInBlanck_en_metadata() throws IOException, URISyntaxException { + URL importTxtUrl = CSVToQuestionConverterTest.class.getResource("question_import_fib_en_metadata.txt"); + Assert.assertNotNull(importTxtUrl); + File importTxt = new File(importTxtUrl.toURI()); + String input = FileUtils.readFileToString(importTxt); + + Translator translator = new KeyTranslator(Locale.ENGLISH); + CSVToQuestionConverter converter = new CSVToQuestionConverter(translator); + converter.parse(input); + + List<ItemAndMetadata> items = converter.getItems(); + Assert.assertNotNull(items); + Assert.assertEquals(1, items.size()); + + ItemAndMetadata importedItem = items.get(0); + Item item = importedItem.getItem(); + Assert.assertNotNull(item); + Assert.assertEquals(Question.TYPE_FIB, item.getQuestion().getType()); + Assert.assertTrue(item.getQuestion() instanceof FIBQuestion); + + FIBQuestion question = (FIBQuestion)item.getQuestion(); + List<Response> responses = question.getResponses(); + Assert.assertNotNull(responses); + Assert.assertEquals(2, responses.size()); + //check java type + for(Response response:responses) { + Assert.assertTrue(response instanceof FIBResponse); + } + + //check type + Assert.assertEquals(FIBResponse.TYPE_CONTENT, ((FIBResponse)responses.get(0)).getType()); + Assert.assertEquals(FIBResponse.TYPE_BLANK, ((FIBResponse)responses.get(1)).getType()); + //check size + Assert.assertEquals(20, ((FIBResponse)responses.get(1)).getSize()); + //check max length + Assert.assertEquals(50, ((FIBResponse)responses.get(1)).getMaxLength()); + } + } \ No newline at end of file diff --git a/src/test/java/org/olat/ims/qti/questionimport/question_import_fib_en_metadata.txt b/src/test/java/org/olat/ims/qti/questionimport/question_import_fib_en_metadata.txt new file mode 100644 index 00000000000..4e9aeca95ba --- /dev/null +++ b/src/test/java/org/olat/ims/qti/questionimport/question_import_fib_en_metadata.txt @@ -0,0 +1,22 @@ +Keyword / Punkte Value Extra + +Type FIB +Title Englisch: Grundwortschatz: ban +Description Eine lange Beschreibung +Keywords Englisch Wortschatz +Coverage +Language de +Subject /Sprache/English/Wortschatz +License CC by-nc-nd +Level Primarschule +Typical learning time +Difficulty index 0.55 +Standard deviation 0.6 +Discrimination index -0.33 +Distractors 1 +Editor OpenOLAT +Editor Version 9.4.2 +Points 1 +Text ban +1 verbannen 20,50 + \ No newline at end of file -- GitLab