diff --git a/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java b/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java index 1d9fab8bb8a9935e560d128f0b5e19c38ba93780..62f1f8d2a4c8b1184923b958304ce284bff438e7 100644 --- a/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java +++ b/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java @@ -182,7 +182,6 @@ public class ItemFileResourceValidator { @Override public void error(SAXParseException exception) throws SAXException { - log.warn("Parsing error importing question item in pool: ", exception); error++; } diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIExportTestResource.java b/src/main/java/org/olat/ims/qti/qpool/QTIExportTestResource.java index 08db68e3fde210cf8d8c92b371572a1b465b4bf0..899dc720a954c8cc44b9ff706fd559c8c07217cf 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIExportTestResource.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIExportTestResource.java @@ -20,6 +20,7 @@ package org.olat.ims.qti.qpool; import java.util.List; +import java.util.Locale; import java.util.zip.ZipOutputStream; import org.olat.modules.qpool.QuestionItemShort; @@ -35,8 +36,8 @@ public class QTIExportTestResource extends AbstractExportTestResource { private final QTIQPoolServiceProvider provider; - public QTIExportTestResource(String encoding, List<QuestionItemShort> items, QTIQPoolServiceProvider provider) { - super(encoding, items); + public QTIExportTestResource(String encoding, Locale locale, List<QuestionItemShort> items, QTIQPoolServiceProvider provider) { + super(encoding, locale, items); this.provider = provider; } 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 123996bc1ef681f548ef987506fa3bba5c38ae2e..7fbe02902f547d1af2dbf7795ccd9e2f3a851f33 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java @@ -313,9 +313,9 @@ public class QTIQPoolServiceProvider implements QPoolSPI { } @Override - public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format) { + public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format, Locale locale) { if(QTIConstants.QTI_12_FORMAT.equals(format.getFormat())) { - return new QTIExportTestResource("UTF-8", items, this); + return new QTIExportTestResource("UTF-8", locale, items, this); }else if(DefaultExportFormat.DOCX_EXPORT_FORMAT.getFormat().equals(format.getFormat())) { return new QTIPoolWordExport(items, I18nModule.getDefaultLocale(), "UTF-8", questionItemDao, qpoolFileStorage); } @@ -324,7 +324,7 @@ public class QTIQPoolServiceProvider implements QPoolSPI { } @Override - public void exportItem(QuestionItemFull item, ZipOutputStream zout, Set<String> names) { + public void exportItem(QuestionItemFull item, ZipOutputStream zout, Locale locale, Set<String> names) { QTIExportProcessor processor = new QTIExportProcessor(qpoolFileStorage); processor.process(item, zout, names); } diff --git a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java index 9803a9bcd5ef07a386b98bf4041d90f68b17b15d..0c9f6d6d3da96ff2c94d1b6320fae2d917dc298d 100644 --- a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java +++ b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java @@ -120,5 +120,15 @@ public class AssessmentTestSessionDAO { } - + public void deleteUserTestSessions(RepositoryEntryRef testEntry) { + String q = "select session from qtiassessmenttestsession session where session.testEntry.key=:testEntryKey"; + List<AssessmentTestSession> sessions = dbInstance.getCurrentEntityManager() + .createQuery(q, AssessmentTestSession.class) + .setParameter("testEntryKey", testEntry.getKey()) + .getResultList(); + for(AssessmentTestSession session:sessions) { + dbInstance.getCurrentEntityManager().remove(session); + } + + } } diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java index 04c928dd20c0f4bc5c52c80f3146bf8900e550da..8b84c8c693471c0a7c864902d2d941eebfb0f1c1 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java @@ -30,6 +30,7 @@ import org.olat.ims.qti21.model.QTI21QuestionType; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.ModalFeedback; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseCondition; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseProcessing; @@ -207,6 +208,17 @@ public abstract class AssessmentItemBuilder { public AssessmentHtmlBuilder getHtmlHelper() { return htmlHelper; } + + public List<String> getInteractionNames() { + List<Interaction> interactions = assessmentItem.getItemBody().findInteractions(); + List<String> interactionNames = new ArrayList<>(interactions.size()); + for(Interaction interaction:interactions) { + String interactionName = interaction.getQtiClassName(); + interactionNames.add(interactionName); + } + + return interactionNames; + } public final void build() { List<OutcomeDeclaration> outcomeDeclarations = assessmentItem.getOutcomeDeclarations(); diff --git a/src/main/java/org/olat/ims/qti21/model/xml/ManifestBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/ManifestBuilder.java index 5b4d604d0c65e7de15e684a3d6524bc4af215f28..cd0d34f9a81069b85e70bee1e636793ad571bab8 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/ManifestBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/ManifestBuilder.java @@ -44,8 +44,6 @@ import org.olat.imscp.xml.manifest.ResourceType; import org.olat.imscp.xml.manifest.ResourcesType; import org.olat.imsmd.xml.manifest.LomType; import org.olat.imsmd.xml.manifest.TechnicalType; -import org.olat.imsqti.xml.manifest.QTIMetadataType; -import org.olat.oo.xml.manifest.OpenOLATMetadataType; /** * manifest @@ -64,16 +62,16 @@ public class ManifestBuilder { private static final OLog log = Tracing.createLoggerFor(ManifestBuilder.class); - public static final String SCHEMA_LOCATIONS = "http://www.imsglobal.org/xsd/imscp_v1p1 http://www.imsglobal.org/xsd/imscp_v1p2.xsd http://www.imsglobal.org/xsd/imsmd_v1p2 http://www.imsglobal.org/xsd/imsmd_v1p2p4.xsd http://www.imsglobal.org/xsd/imsqti_metadata_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_metadata_v2p1.xsd"; - + protected static final org.olat.oo.xml.manifest.ObjectFactory ooObjectFactory = new org.olat.oo.xml.manifest.ObjectFactory(); + protected static final org.olat.imscp.xml.manifest.ObjectFactory cpObjectFactory = new org.olat.imscp.xml.manifest.ObjectFactory(); + protected static final org.olat.imsmd.xml.manifest.ObjectFactory mdObjectFactory = new org.olat.imsmd.xml.manifest.ObjectFactory(); + protected static final org.olat.imsqti.xml.manifest.ObjectFactory qtiObjectFactory = new org.olat.imsqti.xml.manifest.ObjectFactory(); + public static final String ASSESSMENTTEST_MIMETYPE = "text/x-imsqti-test-xml"; public static final String ASSESSMENTITEM_MIMETYPE = "text/x-imsqti-item-xml"; - private static final org.olat.oo.xml.manifest.ObjectFactory ooObjectFactory = new org.olat.oo.xml.manifest.ObjectFactory(); - private static final org.olat.imscp.xml.manifest.ObjectFactory cpObjectFactory = new org.olat.imscp.xml.manifest.ObjectFactory(); - private static final org.olat.imsmd.xml.manifest.ObjectFactory mdObjectFactory = new org.olat.imsmd.xml.manifest.ObjectFactory(); - private static final org.olat.imsqti.xml.manifest.ObjectFactory qtiObjectFactory = new org.olat.imsqti.xml.manifest.ObjectFactory(); - + public static final String SCHEMA_LOCATIONS = "http://www.imsglobal.org/xsd/imscp_v1p1 http://www.imsglobal.org/xsd/imscp_v1p2.xsd http://www.imsglobal.org/xsd/imsmd_v1p2 http://www.imsglobal.org/xsd/imsmd_v1p2p4.xsd http://www.imsglobal.org/xsd/imsqti_metadata_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_metadata_v2p1.xsd"; + private static JAXBContext context; static { try { @@ -177,108 +175,48 @@ public class ManifestBuilder { public String appendAssessmentItem() { String itemId = "id" + UUID.randomUUID().toString(); String itemFileName = itemId + ".xml"; - ResourceType itemResourceType = appendAssessmentItem(itemId, itemFileName); - appendFile(itemResourceType, itemFileName); + appendAssessmentItem(itemId, itemFileName); return itemFileName; } public String appendAssessmentItem(String itemFileName) { String itemId = IdentifierGenerator.newAsString("item"); - ResourceType itemResourceType = appendAssessmentItem(itemId, itemFileName); - appendFile(itemResourceType, itemFileName); + appendAssessmentItem(itemId, itemFileName); return itemFileName; } - - public void setOpenOLATMetadata(ResourceType resource, String questionType) { - OpenOLATMetadataType qtiMetadata = getOpenOLATMetadata(resource, true); - qtiMetadata.setQuestionType(questionType); + public ManifestMetadataBuilder getResourceBuilderByIdentifier(String resourceId) { + ResourceType resourceType = getResourceTypeByIdentifier(resourceId); + MetadataType metadata = getMetadata(resourceType); + return metadata == null ? null : new ManifestMetadataBuilder(metadata); } - /** - * Return the openolat metadata if it exists or, if specified, create - * one and append it to the metadata of the resource. - * - * @param resource The resource with the metadata - * @param create True create the qtiMetadata - * @return - */ - public OpenOLATMetadataType getOpenOLATMetadata(ResourceType resource, boolean create) { - MetadataType metadata = getMetadataTypeByResource(resource, create); - if(metadata == null) return null; - - List<Object> anyMetadataList = metadata.getAny(); - OpenOLATMetadataType ooMetadata = null; - for(Object anyMetadata:anyMetadataList) { - if(anyMetadata instanceof JAXBElement<?> - && ((JAXBElement<?>)anyMetadata).getValue() instanceof OpenOLATMetadataType) { - ooMetadata = (OpenOLATMetadataType)((JAXBElement<?>)anyMetadata).getValue(); + public ResourceType getResourceTypeByIdentifier(String resourceId) { + List<ResourceType> resources = getResourceList(); + for(ResourceType resource:resources) { + if(resourceId.equals(resource.getIdentifier())) { + return resource; } } - - if(ooMetadata == null && create) { - ooMetadata = ooObjectFactory.createOpenOLATMetadataType(); - metadata.getAny().add(ooObjectFactory.createOoMetadata(ooMetadata)); - } - return ooMetadata; + return null; } - public void setQtiMetadata(ResourceType resource, List<String> interactions) { - QTIMetadataType qtiMetadata = getQtiMetadata(resource, true); - - qtiMetadata.getInteractionType().clear(); - for(String interaction:interactions) { - qtiMetadata.getInteractionType().add(interaction); - } + public ManifestMetadataBuilder getResourceBuilderByHref(String href) { + ResourceType resourceType = getResourceTypeByHref(href); + MetadataType metadata = getMetadata(resourceType); + return metadata == null ? null : new ManifestMetadataBuilder(metadata); } - /** - * Return the qti metadata if it exists or if specified, create - * one and append it to the metadata of the resource. - * - * @param resource The resource with the metadata - * @param create True create the qtiMetadata - * @return - */ - public QTIMetadataType getQtiMetadata(ResourceType resource, boolean create) { - MetadataType metadata = getMetadataTypeByResource(resource, create); - if(metadata == null) return null; - - List<Object> anyMetadataList = metadata.getAny(); - QTIMetadataType qtiMetadata = null; - for(Object anyMetadata:anyMetadataList) { - if(anyMetadata instanceof JAXBElement<?> - && ((JAXBElement<?>)anyMetadata).getValue() instanceof QTIMetadataType) { - qtiMetadata = (QTIMetadataType)((JAXBElement<?>)anyMetadata).getValue(); - } - } - - if(qtiMetadata == null && create) { - qtiMetadata = qtiObjectFactory.createQTIMetadataType(); - metadata.getAny().add(qtiObjectFactory.createQtiMetadata(qtiMetadata)); - } - return qtiMetadata; - } - - private MetadataType getMetadataTypeByResource(ResourceType resource, boolean create) { - MetadataType metadata = resource.getMetadata(); - if(metadata == null && create) { + public MetadataType getMetadata(ResourceType resourceType) { + if(resourceType == null) return null; + MetadataType metadata = resourceType.getMetadata(); + if(metadata == null) { metadata = cpObjectFactory.createMetadataType(); - resource.setMetadata(metadata); + resourceType.setMetadata(metadata); } return metadata; } - public ResourceType getResourceTypeByIdentifier(String resourceId) { - List<ResourceType> resources = getResourceList(); - for(ResourceType resource:resources) { - if(resourceId.equals(resource.getIdentifier())) { - return resource; - } - } - return null; - } - public ResourceType getResourceTypeByHref(String href) { List<ResourceType> resources = getResourceList(); for(ResourceType resource:resources) { @@ -314,6 +252,21 @@ public class ManifestBuilder { resource.getFile().add(itemFileType); } + public ManifestMetadataBuilder getMetadataBuilder(ResourceType resource, boolean create) { + MetadataType metadata = getMetadataType(resource, create); + return metadata == null ? null : new ManifestMetadataBuilder(metadata); + + } + + public MetadataType getMetadataType(ResourceType resource, boolean create) { + MetadataType metadata = resource.getMetadata(); + if(metadata == null && create) { + metadata = cpObjectFactory.createMetadataType(); + resource.setMetadata(metadata); + } + return metadata; + } + public void build() { // } diff --git a/src/main/java/org/olat/ims/qti21/model/xml/ManifestFactory.java b/src/main/java/org/olat/ims/qti21/model/xml/ManifestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..c05874f841cf4d64c7c6517653a2455bd159e192 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/model/xml/ManifestFactory.java @@ -0,0 +1,34 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti21.model.xml; + +/** + * + * Initial date: 23.02.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ManifestFactory { + + + + + +} diff --git a/src/main/java/org/olat/ims/qti21/model/xml/ManifestMetadataBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/ManifestMetadataBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..011b8ca4cbb117a5a6add275b2bfcfb38dbcec3d --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/model/xml/ManifestMetadataBuilder.java @@ -0,0 +1,270 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti21.model.xml; + +import java.util.Iterator; +import java.util.List; + +import javax.xml.bind.JAXBElement; + +import org.olat.imscp.xml.manifest.ManifestMetadataType; +import org.olat.imscp.xml.manifest.MetadataType; +import org.olat.imsmd.xml.manifest.DescriptionType; +import org.olat.imsmd.xml.manifest.GeneralType; +import org.olat.imsmd.xml.manifest.LangstringType; +import org.olat.imsmd.xml.manifest.LomType; +import org.olat.imsmd.xml.manifest.TechnicalType; +import org.olat.imsmd.xml.manifest.TitleType; +import org.olat.imsqti.xml.manifest.QTIMetadataType; +import org.olat.oo.xml.manifest.OpenOLATMetadataType; + +/** + * + * Initial date: 23.02.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ManifestMetadataBuilder { + + protected static final org.olat.oo.xml.manifest.ObjectFactory ooObjectFactory = new org.olat.oo.xml.manifest.ObjectFactory(); + protected static final org.olat.imscp.xml.manifest.ObjectFactory cpObjectFactory = new org.olat.imscp.xml.manifest.ObjectFactory(); + protected static final org.olat.imsmd.xml.manifest.ObjectFactory mdObjectFactory = new org.olat.imsmd.xml.manifest.ObjectFactory(); + protected static final org.olat.imsqti.xml.manifest.ObjectFactory qtiObjectFactory = new org.olat.imsqti.xml.manifest.ObjectFactory(); + + public static final String ASSESSMENTTEST_MIMETYPE = "text/x-imsqti-test-xml"; + public static final String ASSESSMENTITEM_MIMETYPE = "text/x-imsqti-item-xml"; + + private MetadataType metadata; + private ManifestMetadataType manifestMetadata; + + + + public ManifestMetadataBuilder() { + metadata = cpObjectFactory.createMetadataType(); + } + + public ManifestMetadataBuilder(MetadataType metadata) { + this.metadata = metadata; + } + + public MetadataType getMetadata() { + return metadata; + } + + public String getTitle() { + GeneralType general = getGeneral(false); + if(general != null) { + TitleType type = getFromAny(TitleType.class, general.getContent()); + return type == null ? null : getFirstString(type.getLangstring()); + } + return null; + } + + public void setTitle(String title, String lang) { + GeneralType general = getGeneral(true); + if(general != null) { + TitleType type = getFromAny(TitleType.class, general.getContent()); + if(type == null) { + type = mdObjectFactory.createTitleType(); + general.getContent().add(mdObjectFactory.createTitle(type)); + } + createOrUpdateFirstLangstring(type.getLangstring(), title, lang); + } + } + + public String getDescription() { + GeneralType general = getGeneral(false); + if(general != null) { + DescriptionType type = getFromAny(DescriptionType.class, general.getContent()); + return type == null ? null : getFirstString(type.getLangstring()); + } + return null; + } + + public void setDescription(String description, String lang) { + GeneralType general = getGeneral(true); + if(general != null) { + DescriptionType type = getFromAny(DescriptionType.class, general.getContent()); + if(type == null) { + type = mdObjectFactory.createDescriptionType(); + general.getContent().add(mdObjectFactory.createDescription(type)); + } + createOrUpdateFirstLangstring(type.getLangstring(), description, lang); + } + } + + public void setTechnicalFormat(String... formats) { + if(formats != null && formats.length > 0 && formats[0] != null) { + TechnicalType technical = getTechnical(true); + clearFromAny("format", technical.getContent()); + for(int i=0; i<formats.length; i++) { + technical.getContent().add(mdObjectFactory.createFormat(formats[i])); + } + } + } + + public void createOrUpdateFirstLangstring(List<LangstringType> langStrings, String value, String lang) { + if(langStrings.isEmpty()) { + langStrings.add(createString(value, lang)); + } else { + langStrings.get(0).setValue(value); + langStrings.get(0).setLang(lang); + } + } + + public LangstringType createString(String value, String lang) { + LangstringType string = mdObjectFactory.createLangstringType(); + string.setLang(lang); + string.setValue(value); + return string; + } + + public String getFirstString(List<LangstringType> langStrings) { + String firstString = null; + if(langStrings != null && langStrings.size() > 0) { + firstString = langStrings.get(0).getValue(); + } + return firstString; + } + + public TechnicalType getTechnical(boolean create) { + LomType lom = getLom(create); + if(lom == null) return null; + + TechnicalType technical = lom.getTechnical(); + if(technical == null && create) { + technical = mdObjectFactory.createTechnicalType(); + lom.setTechnical(technical); + } + return technical; + } + + public GeneralType getGeneral(boolean create) { + LomType lom = getLom(create); + if(lom == null) return null; + + GeneralType general = lom.getGeneral(); + if(general == null && create) { + general = mdObjectFactory.createGeneralType(); + lom.setGeneral(general); + } + return general; + } + + public LomType getLom(boolean create) { + LomType lom = getFromAny(LomType.class, getMetadataList()); + if(lom == null && create) { + lom = mdObjectFactory.createLomType(); + getMetadataList().add(mdObjectFactory.createLom(lom)); + } + return lom; + } + + /** + * Return the openolat metadata if it exists or, if specified, create + * one and append it to the metadata of the resource. + * + * @param resource The resource with the metadata + * @param create True create the qtiMetadata + * @return + */ + public OpenOLATMetadataType getOpenOLATMetadata(boolean create) { + List<Object> anyMetadataList = getMetadataList(); + OpenOLATMetadataType ooMetadata = null; + for(Object anyMetadata:anyMetadataList) { + if(anyMetadata instanceof JAXBElement<?> + && ((JAXBElement<?>)anyMetadata).getValue() instanceof OpenOLATMetadataType) { + ooMetadata = (OpenOLATMetadataType)((JAXBElement<?>)anyMetadata).getValue(); + } + } + + if(ooMetadata == null && create) { + ooMetadata = ooObjectFactory.createOpenOLATMetadataType(); + getMetadataList().add(ooObjectFactory.createOoMetadata(ooMetadata)); + } + return ooMetadata; + } + + public void setOpenOLATMetadata(String questionType) { + OpenOLATMetadataType qtiMetadata = getOpenOLATMetadata(true); + qtiMetadata.setQuestionType(questionType); + } + + /** + * Return the qti metadata if it exists or if specified, create + * one and append it to the metadata of the resource. + * + * @param resource The resource with the metadata + * @param create True create the qtiMetadata + * @return + */ + public QTIMetadataType getQtiMetadata(boolean create) { + List<Object> anyMetadataList = getMetadataList(); + QTIMetadataType qtiMetadata = null; + for(Object anyMetadata:anyMetadataList) { + if(anyMetadata instanceof JAXBElement<?> + && ((JAXBElement<?>)anyMetadata).getValue() instanceof QTIMetadataType) { + qtiMetadata = (QTIMetadataType)((JAXBElement<?>)anyMetadata).getValue(); + } + } + + if(qtiMetadata == null && create) { + qtiMetadata = qtiObjectFactory.createQTIMetadataType(); + getMetadataList().add(qtiObjectFactory.createQtiMetadata(qtiMetadata)); + } + return qtiMetadata; + } + + public void setQtiMetadata(List<String> interactions) { + QTIMetadataType qtiMetadata = getQtiMetadata(true); + + qtiMetadata.getInteractionType().clear(); + for(String interaction:interactions) { + qtiMetadata.getInteractionType().add(interaction); + } + } + + @SuppressWarnings("unchecked") + private <U> U getFromAny(Class<U> type, List<Object> anyList) { + U object = null; + for(Object any:anyList) { + if(any instanceof JAXBElement<?> + && ((JAXBElement<?>)any).getValue().getClass().equals(type)) { + object = (U)((JAXBElement<?>)any).getValue(); + } + } + return object; + } + + private void clearFromAny(String type, List<Object> anyList) { + for(Iterator<Object> anyIterator=anyList.iterator(); anyIterator.hasNext(); ) { + Object any = anyIterator.next(); + if(any instanceof JAXBElement<?> + && ((JAXBElement<?>)any).getName().getLocalPart().equals(type)) { + anyIterator.remove(); + } + } + } + + public List<Object> getMetadataList() { + return metadata == null ? manifestMetadata.getAny() : metadata.getAny(); + } + +} diff --git a/src/main/java/org/olat/ims/qti21/pool/AssessmentItemFileResourceValidator.java b/src/main/java/org/olat/ims/qti21/pool/AssessmentItemFileResourceValidator.java index d7066c3bb0652d9b0602002dd36c0b6fa8cfba2e..4f24d9d4353558187c48db38275ae9e46331a765 100644 --- a/src/main/java/org/olat/ims/qti21/pool/AssessmentItemFileResourceValidator.java +++ b/src/main/java/org/olat/ims/qti21/pool/AssessmentItemFileResourceValidator.java @@ -27,26 +27,22 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; import org.apache.commons.io.IOUtils; -import org.dom4j.Document; -import org.dom4j.DocumentType; -import org.dom4j.io.SAXReader; -import org.dom4j.io.SAXValidator; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.io.ShieldInputStream; import org.olat.core.util.vfs.VFSLeaf; -import org.olat.ims.resources.IMSEntityResolver; import org.xml.sax.Attributes; import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; +import uk.ac.ed.ph.jqtiplus.xmlutils.XmlFactories; + /** * * Validate an file @@ -90,7 +86,7 @@ public class AssessmentItemFileResourceValidator { try { ZipEntry oEntr = oZip.getNextEntry(); while (oEntr != null) { - if (!oEntr.isDirectory()) { + if (!oEntr.isDirectory() && !"imsmanifest.xml".equals(oEntr.getName())) { if(validateXml(new ShieldInputStream(oZip))) { valid = true; } @@ -109,53 +105,19 @@ public class AssessmentItemFileResourceValidator { return valid; } - + private boolean validateXml(InputStream in) { - boolean valid = false; - Document doc = readDocument(in); - if(doc != null) { - DocumentType docType = doc.getDocType(); - if(docType == null) { - doc.addDocType("questestinterop", null, "ims_qtiasiv1p2p1.dtd"); - } - valid = validateDocument(doc); - } - return valid; - } - - private Document readDocument(InputStream in) { try { - SAXReader reader = new SAXReader(); - reader.setEntityResolver(new IMSEntityResolver()); - reader.setValidation(false); - return reader.read(in, ""); - } catch (Exception e) { - return null; - } - } - - private boolean validateDocument(Document in) { - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setValidating(true); - factory.setNamespaceAware(true); - + XMLReader reader = XmlFactories.newSAXParser().getXMLReader(); + SimpleErrorHandler errorHandler = new SimpleErrorHandler(); - ItemContentHandler contentHandler = new ItemContentHandler(); - - SAXParser parser = factory.newSAXParser(); - XMLReader reader = parser.getXMLReader(); - reader.setEntityResolver(new IMSEntityResolver()); reader.setErrorHandler(errorHandler); + AssessmentItemContentHandler contentHandler = new AssessmentItemContentHandler(); reader.setContentHandler(contentHandler); + reader.parse(new InputSource(in)); - SAXValidator validator = new SAXValidator(reader); - validator.validate(in); - - return errorHandler.isValid() && contentHandler.isItem(); - } catch (ParserConfigurationException e) { - return false; - } catch (SAXException e) { + return errorHandler.isValid() && contentHandler.isAssessmentItem(); + } catch (ParserConfigurationException | SAXException e) { return false; } catch (Exception e) { return false; @@ -176,7 +138,6 @@ public class AssessmentItemFileResourceValidator { @Override public void error(SAXParseException exception) throws SAXException { - log.warn("Parsing error importing question item in pool: ", exception); error++; } @@ -186,32 +147,18 @@ public class AssessmentItemFileResourceValidator { } } - private static class ItemContentHandler extends DefaultHandler { - private boolean interop; - private boolean item; + private static class AssessmentItemContentHandler extends DefaultHandler { + private boolean assessmentItem; - public boolean isItem() { - return item; + public boolean isAssessmentItem() { + return assessmentItem; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - - if("questestinterop".equals(qName)) { - interop = true; - } else if("item".equals(localName) || "item".equals(qName)) { - if(interop) { - item = true; - } - } - } - - @Override - public void endElement(String uri, String localName, String qName) - throws SAXException { - if("questestinterop".equals(qName)) { - interop = false; + if("assessmentItem".equals(qName)) { + assessmentItem = true; } } } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java index ba7cb764237c7d1702017ddc83508b636bf81eed..ec3c323042d7c4247520336db1bae4311adf5b2d 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java @@ -24,6 +24,7 @@ import java.io.FileOutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import java.util.Locale; import org.olat.core.helpers.Settings; import org.olat.core.logging.OLog; @@ -51,6 +52,7 @@ import org.olat.ims.qti21.model.xml.AssessmentItemFactory; import org.olat.ims.qti21.model.xml.AssessmentTestBuilder; import org.olat.ims.qti21.model.xml.AssessmentTestFactory; import org.olat.ims.qti21.model.xml.ManifestBuilder; +import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder; import org.olat.ims.qti21.model.xml.ModalFeedbackBuilder; import org.olat.ims.qti21.model.xml.interactions.ChoiceAssessmentItemBuilder.ScoreEvaluation; import org.olat.ims.qti21.model.xml.interactions.EssayAssessmentItemBuilder; @@ -87,13 +89,15 @@ public class QTI12To21Converter { private static final OLog log = Tracing.createLoggerFor(QTI12To21Converter.class); + private final Locale locale; private final File unzippedDirRoot; private final QtiSerializer qtiSerializer = new QtiSerializer(null); private final AssessmentHtmlBuilder htmlBuilder = new AssessmentHtmlBuilder(qtiSerializer); private final ManifestBuilder manifest; - public QTI12To21Converter(File unzippedDirRoot) { + public QTI12To21Converter(File unzippedDirRoot, Locale locale) { + this.locale = locale; this.unzippedDirRoot = unzippedDirRoot; manifest = ManifestBuilder.createAssessmentTestBuilder(); } @@ -210,11 +214,21 @@ public class QTI12To21Converter { itemRef.setHref(new URI(itemFile.getName())); assessmentSection.getSectionParts().add(itemRef); persistAssessmentObject(itemFile, assessmentItem); - manifest.appendAssessmentItem(itemFile.getName()); + appendResourceAndMetadata(item, itemBuilder, itemFile); } } } + private void appendResourceAndMetadata(Item item, AssessmentItemBuilder itemBuilder, File itemFile) { + manifest.appendAssessmentItem(itemFile.getName()); + ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(itemFile.getName()); + metadata.setTechnicalFormat(ManifestBuilder.ASSESSMENTITEM_MIMETYPE); + metadata.setQtiMetadata(itemBuilder.getInteractionNames()); + metadata.setOpenOLATMetadata(itemBuilder.getQuestionType().getPrefix()); + metadata.setTitle(item.getTitle(), locale.getLanguage()); + metadata.setDescription(item.getObjectives(), locale.getLanguage()); + } + public boolean persistAssessmentObject(File resourceFile, AssessmentObject assessmentObject) { try(FileOutputStream out = new FileOutputStream(resourceFile)) { qtiSerializer.serializeJqtiObject(assessmentObject, out); diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java index 045a90bc45f44a640e17929a8724c20cb285da84..7dc70f8a4c1efab4906748ddcdfd086cfa075ce6 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java @@ -26,6 +26,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -33,12 +34,14 @@ import java.util.zip.ZipOutputStream; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; import org.olat.core.util.ZipUtil; import org.olat.core.util.io.ShieldOutputStream; import org.olat.core.util.vfs.VFSLeaf; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.xml.AssessmentTestFactory; import org.olat.ims.qti21.model.xml.ManifestBuilder; +import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder; import org.olat.modules.qpool.QuestionItemFull; import org.olat.modules.qpool.manager.QPoolFileStorage; @@ -57,10 +60,12 @@ public class QTI21ExportProcessor { private static final OLog log = Tracing.createLoggerFor(QTI21ExportProcessor.class); + private final Locale locale; private final QTI21Service qtiService; private final QPoolFileStorage qpoolFileStorage; - public QTI21ExportProcessor(QTI21Service qtiService, QPoolFileStorage qpoolFileStorage) { + public QTI21ExportProcessor(QTI21Service qtiService, QPoolFileStorage qpoolFileStorage, Locale locale) { + this.locale = locale; this.qtiService = qtiService; this.qpoolFileStorage = qpoolFileStorage; } @@ -136,11 +141,12 @@ public class QTI21ExportProcessor { String itemFilename = itemFile.getName(); //enrichScore(itemEl); - //enrichWithMetadata(fullItem, itemEl); //collectResources(itemEl, container, materials); FileUtils.bcopy(itemFile, new File(directory, rootFilename), ""); AssessmentTestFactory.appendAssessmentItem(section, itemFilename); manifest.appendAssessmentItem(itemFilename); + ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(itemFilename); + enrichWithMetadata(qitem, metadata); } try(FileOutputStream out = new FileOutputStream(new File(directory, assessmentTestFilename))) { @@ -177,12 +183,13 @@ public class QTI21ExportProcessor { String itemFilename = itemFile.getName(); //enrichScore(itemEl); - //enrichWithMetadata(fullItem, itemEl); //collectResources(itemEl, container, materials); ZipUtil.addFileToZip(itemFilename, itemFile, zout); AssessmentTestFactory.appendAssessmentItem(section, itemFilename); manifest.appendAssessmentItem(itemFilename); + ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(itemFilename); + enrichWithMetadata(qitem, metadata); } zout.putNextEntry(new ZipEntry(assessmentTestFilename)); @@ -197,6 +204,21 @@ public class QTI21ExportProcessor { } } + private void enrichWithMetadata(QuestionItemFull qitem, ManifestMetadataBuilder metadata) { + String lang = qitem.getLanguage(); + if(!StringHelper.containsNonWhitespace(lang)) { + lang = locale.getLanguage(); + } + if(StringHelper.containsNonWhitespace(qitem.getTitle())) { + metadata.setTitle(qitem.getTitle(), lang); + } + if(StringHelper.containsNonWhitespace(qitem.getDescription())) { + metadata.setDescription(qitem.getDescription(), lang); + } + + + } + private static final class AssessmentItemsAndResources { private final Set<String> paths = new HashSet<String>(); private final List<ResolvedAssessmentItem> itemEls = new ArrayList<ResolvedAssessmentItem>(); diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportTestResource.java b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportTestResource.java index 3bf2b888c0b4d58bb54a84b3871f1ec749c44f27..ccaeccd1ba483b6e2adf8b438af44104b7b62cac 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportTestResource.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportTestResource.java @@ -20,6 +20,7 @@ package org.olat.ims.qti21.pool; import java.util.List; +import java.util.Locale; import java.util.zip.ZipOutputStream; import org.olat.modules.qpool.QuestionItemShort; @@ -35,14 +36,14 @@ public class QTI21ExportTestResource extends AbstractExportTestResource { private final QTI21QPoolServiceProvider qtiPoolServiceProvider; - public QTI21ExportTestResource(String encoding, List<QuestionItemShort> items, + public QTI21ExportTestResource(String encoding, Locale locale, List<QuestionItemShort> items, QTI21QPoolServiceProvider qti21qPoolServiceProvider) { - super(encoding, items); + super(encoding, locale, items); this.qtiPoolServiceProvider = qti21qPoolServiceProvider; } @Override protected void exportTest(List<QuestionItemShort> items, ZipOutputStream zout) { - qtiPoolServiceProvider.assembleTest(items, zout); + qtiPoolServiceProvider.assembleTest(items, getLocale(), zout); } } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java b/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java index 3266b5ca2d7893a1b7a532fb49c5007d39c1cbf2..3c817b83d4bb87cd76aa591f6089664fb9402123 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java @@ -23,7 +23,6 @@ import java.io.File; import java.util.List; import java.util.Locale; -import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.util.StringHelper; import org.olat.ims.qti.qpool.QTIMetadataConverter; @@ -64,10 +63,9 @@ public class QTI21ImportProcessor { private final TaxonomyLevelDAO taxonomyLevelDao; private final QEducationalContextDAO qEduContextDao; - public QTI21ImportProcessor(Identity owner, Locale defaultLocale, String filename, File file, + public QTI21ImportProcessor(Identity owner, Locale defaultLocale, QuestionItemDAO questionItemDao, QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao, - TaxonomyLevelDAO taxonomyLevelDao, QLicenseDAO qLicenseDao, QPoolFileStorage qpoolFileStorage, - DB dbInstance) { + TaxonomyLevelDAO taxonomyLevelDao, QLicenseDAO qLicenseDao, QPoolFileStorage qpoolFileStorage) { this.owner = owner; this.defaultLocale = defaultLocale; this.qLicenseDao = qLicenseDao; @@ -78,7 +76,14 @@ public class QTI21ImportProcessor { this.taxonomyLevelDao = taxonomyLevelDao; } - public List<QuestionItem> process() { + public List<QuestionItem> process(File file) { + + //export zip file + + //metadata copy in question + + + return null; } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java index f177c4cf5d12b4ac33514a82bcc611d14d464245..3b2536bde028f2ed6580596d80e705b9136d19c1 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java @@ -189,25 +189,25 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { @Override public List<QuestionItem> importItems(Identity owner, Locale defaultLocale, String filename, File file) { - QTI21ImportProcessor processor = new QTI21ImportProcessor(owner, defaultLocale, filename, file, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); - return processor.process(); + QTI21ImportProcessor processor = new QTI21ImportProcessor(owner, defaultLocale, + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage); + return processor.process(file); } @Override - public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format) { + public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format, Locale locale) { if(QTI21Constants.QTI_21_FORMAT.equals(format.getFormat())) { - return new QTI21ExportTestResource("UTF-8", items, this); + return new QTI21ExportTestResource("UTF-8", locale, items, this); } return null; } @Override - public void exportItem(QuestionItemFull item, ZipOutputStream zout, Set<String> names) { - QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage); + public void exportItem(QuestionItemFull item, ZipOutputStream zout, Locale locale, Set<String> names) { + QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage, locale); processor.process(item, zout, names); } @@ -252,8 +252,8 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { AssessmentItemMetadata itemMetadata = new AssessmentItemMetadata(); itemMetadata.setQuestionType(type); - QTI21ImportProcessor processor = new QTI21ImportProcessor(identity, locale, null, null, - questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance); + QTI21ImportProcessor processor = new QTI21ImportProcessor(identity, locale, + questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage); QuestionItemImpl qitem = processor.processItem(assessmentItem, "", null, "OpenOLAT", Settings.getVersion(), itemMetadata); VFSContainer baseDir = qpoolFileStorage.getContainer(qitem.getDirectory()); @@ -275,8 +275,8 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { * @param editorContainer * @return */ - public AssessmentItem exportToQTIEditor(QuestionItemShort qitem, File editorContainer) throws IOException { - QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage); + public AssessmentItem exportToQTIEditor(QuestionItemShort qitem, Locale locale, File editorContainer) throws IOException { + QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage, locale); QuestionItemFull fullItem = questionItemDao.loadById(qitem.getKey()); ResolvedAssessmentItem resolvedAssessmentItem = processor.exportToQTIEditor(fullItem, editorContainer); AssessmentItem assessmentItem = resolvedAssessmentItem.getItemLookup().extractAssumingSuccessful(); @@ -284,22 +284,22 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { return assessmentItem; } - public void assembleTest(List<QuestionItemShort> items, ZipOutputStream zout) { + public void assembleTest(List<QuestionItemShort> items, Locale locale, ZipOutputStream zout) { List<Long> itemKeys = new ArrayList<Long>(); for(QuestionItemShort item:items) { itemKeys.add(item.getKey()); } List<QuestionItemFull> fullItems = questionItemDao.loadByIds(itemKeys); - QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage); + QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage, locale); processor.assembleTest(fullItems, zout); } - public void exportToEditorPackage(File exportDir, List<QuestionItemShort> items) { + public void exportToEditorPackage(File exportDir, List<QuestionItemShort> items, Locale locale) { List<Long> itemKeys = toKeys(items); List<QuestionItemFull> fullItems = questionItemDao.loadByIds(itemKeys); - QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage); + QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage, locale); processor.assembleTest(fullItems, exportDir); } @@ -308,9 +308,9 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { * * @param qtiEditorPackage */ - public boolean convertFromEditorPackage(QTIEditorPackage qtiEditorPackage, File unzippedDirRoot) { + public boolean convertFromEditorPackage(QTIEditorPackage qtiEditorPackage, File unzippedDirRoot, Locale locale) { try { - QTI12To21Converter converter = new QTI12To21Converter(unzippedDirRoot); + QTI12To21Converter converter = new QTI12To21Converter(unzippedDirRoot, locale); converter.convert(qtiEditorPackage); return true; } catch (URISyntaxException e) { diff --git a/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java b/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java index 98b53ddf53de926f028076b1d4ec5695a92494a1..7aab83a796b4f22a1071b0b248f5ee0e82f74211 100644 --- a/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java +++ b/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java @@ -20,9 +20,14 @@ package org.olat.ims.qti21.questionimport; import java.math.BigDecimal; +import java.util.Locale; +import org.olat.core.util.StringHelper; +import org.olat.core.util.filter.FilterFactory; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; +import org.olat.ims.qti21.model.xml.ManifestBuilder; +import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder; /** * @@ -33,8 +38,9 @@ import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; public class AssessmentItemAndMetadata { private final AssessmentItemBuilder item; + private final QTI21QuestionType questionType; - private QTI21QuestionType questionType; + private String description; private String language; private String taxonomyPath; private String keywords; @@ -53,12 +59,21 @@ public class AssessmentItemAndMetadata { public AssessmentItemAndMetadata(AssessmentItemBuilder item) { this.item = item; + questionType = item.getQuestionType(); } public AssessmentItemBuilder getItemBuilder() { return item; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + public BigDecimal getDifficulty() { return difficulty; } @@ -182,4 +197,18 @@ public class AssessmentItemAndMetadata { public void setHasError(boolean hasError) { this.hasError = hasError; } + + public void transfer(ManifestMetadataBuilder metadata, Locale locale) { + if(questionType != null) { + metadata.setOpenOLATMetadata(questionType.getPrefix()); + } + metadata.setTechnicalFormat(ManifestBuilder.ASSESSMENTITEM_MIMETYPE); + if(StringHelper.containsNonWhitespace(item.getTitle())) { + metadata.setTitle(item.getTitle(), locale.getLanguage()); + } + if(StringHelper.containsNonWhitespace(description)) { + String cleanedDescription = FilterFactory.getHtmlTagsFilter().filter(description); + metadata.setDescription(cleanedDescription, locale.getLanguage()); + } + } } diff --git a/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java b/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java index 9b4de39de04e8c5a763b19082b459b8b3dca8832..05f8b6d8b96ee435d8363e7f2647c334c11ec03c 100644 --- a/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java +++ b/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java @@ -97,7 +97,7 @@ public class CSVToAssessmentItemConverter { case "titel": case "title": processTitle(parts); break; case "beschreibung": - case "description": break; + case "description": processDescription(parts); break; case "frage": case "question": processQuestion(parts); break; case "punkte": @@ -130,6 +130,15 @@ public class CSVToAssessmentItemConverter { default: processChoice(parts); } } + + private void processDescription(String[] parts) { + if(currentItem == null || parts.length < 2) return; + + String description = parts[1]; + if(StringHelper.containsNonWhitespace(description)) { + currentItem.setDescription(description); + } + } private void processLevel(String[] parts) { if(currentItem == null || parts.length < 2) return; diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java index 49756bf0ebb1b740eec77b0357a44105ce5c3216..e031010d124fb7ce11b86f5f29ff2028d8d317ae 100644 --- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java +++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java @@ -66,6 +66,7 @@ import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ResourceEvaluation; import org.olat.ims.qti.editor.QTIEditorPackage; import org.olat.ims.qti21.QTI21Service; +import org.olat.ims.qti21.manager.AssessmentTestSessionDAO; import org.olat.ims.qti21.model.IdentifierGenerator; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.AssessmentItemFactory; @@ -115,6 +116,8 @@ public class QTI21AssessmentTestHandler extends FileHandler { private RepositoryService repositoryService; @Autowired private QTI21QPoolServiceProvider qpoolServiceProvider; + @Autowired + private AssessmentTestSessionDAO assessmentTestSessionDao; @Override public String getSupportedType() { @@ -145,10 +148,10 @@ public class QTI21AssessmentTestHandler extends FileHandler { } if(createObject instanceof QItemList) { QItemList itemToImport = (QItemList)createObject; - qpoolServiceProvider.exportToEditorPackage(repositoryDir, itemToImport.getItems()); + qpoolServiceProvider.exportToEditorPackage(repositoryDir, itemToImport.getItems(), locale); } else if(createObject instanceof QTIEditorPackage) { QTIEditorPackage testToConvert = (QTIEditorPackage)createObject; - qpoolServiceProvider.convertFromEditorPackage(testToConvert, repositoryDir); + qpoolServiceProvider.convertFromEditorPackage(testToConvert, repositoryDir, locale); } else { createMinimalAssessmentTest(displayname, repositoryDir); } @@ -358,11 +361,14 @@ public class QTI21AssessmentTestHandler extends FileHandler { @Override public boolean readyToDelete(RepositoryEntry entry, Identity identity, Roles roles, Locale locale, ErrorList errors) { - boolean ready = super.readyToDelete(entry, identity, roles, locale, errors); - if(ready) { - //update / remove assessment test sessions - } - return ready; + return super.readyToDelete(entry, identity, roles, locale, errors); + } + + @Override + public boolean cleanupOnDelete(RepositoryEntry entry, OLATResourceable res) { + boolean clean = super.cleanupOnDelete(entry, res); + assessmentTestSessionDao.deleteUserTestSessions(entry); + return clean; } @Override diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java index c57a9ed077be94622a2d3042c2c79731bb5b4243..d2401717f7341809330a0169ee60067f5789b7cd 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java @@ -34,6 +34,8 @@ import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.QTI21QuestionTypeDetector; import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; +import org.olat.ims.qti21.model.xml.ManifestBuilder; +import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder; import org.olat.ims.qti21.model.xml.interactions.EssayAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.KPrimAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder; @@ -67,10 +69,12 @@ public class AssessmentItemEditorController extends BasicController { private final TabbedPane tabbedPane; private final VelocityContainer mainVC; - private Controller itemEditor, scoreEditor, feedbackEditor; + private MetadataEditorController metadataEditor; private AssessmentItemDisplayController displayCtrl; + private Controller itemEditor, scoreEditor, feedbackEditor; private AssessmentItemBuilder itemBuilder; + private ManifestMetadataBuilder metadataBuilder; @Autowired private QTI21Service qtiService; @@ -99,9 +103,11 @@ public class AssessmentItemEditorController extends BasicController { } public AssessmentItemEditorController(UserRequest ureq, WindowControl wControl, RepositoryEntry testEntry, - ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef, File unzippedDirectory) { + ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef, ManifestMetadataBuilder metadataBuilder, + File unzippedDirectory) { super(ureq, wControl); this.itemRef = itemRef; + this.metadataBuilder = metadataBuilder; this.resolvedAssessmentItem = resolvedAssessmentItem; mainVC = createVelocityContainer("assessment_item_editor"); @@ -130,10 +136,8 @@ public class AssessmentItemEditorController extends BasicController { } private void initItemEditor(UserRequest ureq) { - - - AssessmentItem item = resolvedAssessmentItem.getItemLookup().getRootNodeHolder().getRootNode(); + QTI21QuestionType type = QTI21QuestionTypeDetector.getType(item); switch(type) { case sc: itemBuilder = initSingleChoiceEditors(ureq, item); break; @@ -142,7 +146,12 @@ public class AssessmentItemEditorController extends BasicController { case essay: itemBuilder = initEssayEditors(ureq, item); break; default: initItemCreatedByUnkownEditor(ureq); break; } - + + if(metadataBuilder != null) { + metadataEditor = new MetadataEditorController(ureq, getWindowControl(), metadataBuilder); + listenTo(metadataEditor); + tabbedPane.addTab(translate("form.metadata"), metadataEditor.getInitialComponent()); + } } private void initItemCreatedByUnkownEditor(UserRequest ureq) { @@ -223,14 +232,22 @@ public class AssessmentItemEditorController extends BasicController { AssessmentItemEvent aie = (AssessmentItemEvent)event; if(AssessmentItemEvent.ASSESSMENT_ITEM_CHANGED.equals(aie.getCommand())) { doBuildAndSaveAssessmentItem(); + doBuildAndCommitMetadata(); fireEvent(ureq, new AssessmentItemEvent(aie.getCommand(), aie.getAssessmentItem(), itemRef, aie.getQuestionType())); } } + } else if(metadataEditor == source) { + if(event == Event.CHANGED_EVENT) { + doBuildAndCommitMetadata(); + AssessmentItem item = resolvedAssessmentItem.getItemLookup().getRootNodeHolder().getRootNode(); + fireEvent(ureq, new AssessmentItemEvent(AssessmentItemEvent.ASSESSMENT_ITEM_METADATA_CHANGED, item, itemRef, null)); + } } super.event(ureq, source, event); } private void doBuildAndSaveAssessmentItem() { + //update assessment item file if(itemBuilder != null) { itemBuilder.build(); } @@ -238,4 +255,17 @@ public class AssessmentItemEditorController extends BasicController { File itemFile = new File(itemUri); qtiService.updateAssesmentObject(itemFile, resolvedAssessmentItem); } + + private void doBuildAndCommitMetadata() { + if(metadataBuilder == null) return; + + //update manifest + metadataBuilder.setTechnicalFormat(ManifestBuilder.ASSESSMENTITEM_MIMETYPE); + metadataBuilder.setQtiMetadata(itemBuilder.getInteractionNames()); + if(itemBuilder != null) { + metadataBuilder.setOpenOLATMetadata(itemBuilder.getQuestionType().getPrefix()); + } else { + metadataBuilder.setOpenOLATMetadata(QTI21QuestionType.unkown.getPrefix()); + } + } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java index 93539f6bda459a676630aa7fe3d4c1aec3ec3ba3..b49323a2f4ce12454b32db8bbaa08f58feee11de 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java @@ -23,7 +23,6 @@ import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; import java.util.List; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; @@ -55,10 +54,10 @@ import org.olat.fileresource.FileResourceManager; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.IdentifierGenerator; -import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; import org.olat.ims.qti21.model.xml.AssessmentTestFactory; import org.olat.ims.qti21.model.xml.ManifestBuilder; +import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder; import org.olat.ims.qti21.model.xml.interactions.EssayAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.KPrimAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder; @@ -82,7 +81,6 @@ import org.olat.repository.ui.RepositoryEntryRuntimeController.ToolbarAware; import org.springframework.beans.factory.annotation.Autowired; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; -import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; import uk.ac.ed.ph.jqtiplus.node.test.AbstractPart; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection; @@ -244,12 +242,15 @@ public class AssessmentTestComposerController extends MainLayoutBasicController if(AssessmentSectionEvent.ASSESSMENT_SECTION_CHANGED.equals(ase.getCommand())) { doSaveAssessmentTest(); doUpdate(ase.getSection().getIdentifier(), ase.getSection().getTitle()); + doSaveManifest(); } } else if(event instanceof AssessmentItemEvent) { AssessmentItemEvent aie = (AssessmentItemEvent)event; if(AssessmentItemEvent.ASSESSMENT_ITEM_CHANGED.equals(aie.getCommand())) { - doUpdateManifest(aie.getAssessmentItemRef(), aie.getAssessmentItem(), aie.getQuestionType()); doUpdate(aie.getAssessmentItemRef().getIdentifier(), aie.getAssessmentItem().getTitle()); + doSaveManifest(); + } else if(AssessmentItemEvent.ASSESSMENT_ITEM_METADATA_CHANGED.equals(aie.getCommand())) { + doSaveManifest(); } } else if(selectQItemCtrl == source) { cmc.deactivate(); @@ -352,10 +353,10 @@ public class AssessmentTestComposerController extends MainLayoutBasicController try { AssessmentSection section = (AssessmentSection)sectionNode.getUserObject(); for(QuestionItemView item:items) { - AssessmentItem assessmentItem = qti21QPoolServiceProvider.exportToQTIEditor(item, unzippedDirRoot); - String itemId = doInsert(section, assessmentItem); + AssessmentItem assessmentItem = qti21QPoolServiceProvider.exportToQTIEditor(item, getLocale(), unzippedDirRoot); + AssessmentItemRef itemRef = doInsert(section, assessmentItem); if(firstItemId == null) { - firstItemId = itemId; + firstItemId = itemRef.getIdentifier().toString(); } } } catch (IOException | URISyntaxException e) { @@ -381,10 +382,14 @@ public class AssessmentTestComposerController extends MainLayoutBasicController List<AssessmentItemAndMetadata> itemsAndMetadata = importPackage.getItems(); for(AssessmentItemAndMetadata itemAndMetadata:itemsAndMetadata) { - AssessmentItem assessmentItem = itemAndMetadata.getItemBuilder().getAssessmentItem(); - String itemId = doInsert(section, assessmentItem); + AssessmentItemBuilder itemBuilder = itemAndMetadata.getItemBuilder(); + AssessmentItem assessmentItem = itemBuilder.getAssessmentItem(); + AssessmentItemRef itemRef = doInsert(section, assessmentItem); + ManifestMetadataBuilder metadata = manifestBuilder.getResourceBuilderByHref(itemRef.getHref().toString()); + metadata.setQtiMetadata(itemBuilder.getInteractionNames()); + itemAndMetadata.transfer(metadata, getLocale()); if(firstItemId == null) { - firstItemId = itemId; + firstItemId = itemRef.getIdentifier().toString(); } } } catch (URISyntaxException e) { @@ -392,6 +397,9 @@ public class AssessmentTestComposerController extends MainLayoutBasicController logError("", e); } + //persist metadata + manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); + updateTreeModel(); TreeNode newItemNode = menuTree.getTreeModel().getNodeById(firstItemId); @@ -400,7 +408,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController partEditorFactory(ureq, newItemNode); } - private String doInsert(AssessmentSection section, AssessmentItem assessmentItem) + private AssessmentItemRef doInsert(AssessmentSection section, AssessmentItem assessmentItem) throws URISyntaxException { AssessmentItemRef itemRef = new AssessmentItemRef(section); String itemId = assessmentItem.getIdentifier(); @@ -417,7 +425,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController manifestBuilder.appendAssessmentItem(itemFile.getName()); manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); - return itemId; + return itemRef; } private TreeNode doOpenFirstItem() { @@ -538,25 +546,8 @@ public class AssessmentTestComposerController extends MainLayoutBasicController qtiService.updateAssesmentObject(testFile, resolvedAssessmentTest); } - private void doUpdateManifest(AssessmentItemRef ref, AssessmentItem item, QTI21QuestionType questionType) { - URI itemUri = resolvedAssessmentTest.getResolvedAssessmentItem(ref).getItemLookup().getSystemId(); - File itemFile = new File(itemUri); - String relativePathToManifest = unzippedDirRoot.toPath().relativize(itemFile.toPath()).toString(); - - ResourceType resource = manifestBuilder.getResourceTypeByHref(relativePathToManifest); - if(resource != null) { - List<Interaction> interactions = item.getItemBody().findInteractions(); - List<String> interactionNames = new ArrayList<>(interactions.size()); - for(Interaction interaction:interactions) { - String interactionName = interaction.getQtiClassName(); - interactionNames.add(interactionName); - } - manifestBuilder.setQtiMetadata(resource, interactionNames); - if(questionType != null) { - manifestBuilder.setOpenOLATMetadata(resource, questionType.getPrefix()); - } - manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); - } + private void doSaveManifest() { + this.manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); } private void doUpdate(Identifier identifier, String newTitle) { @@ -598,8 +589,9 @@ public class AssessmentTestComposerController extends MainLayoutBasicController currentEditorCtrl = new BadResourceController(ureq, getWindowControl(), item.getItemLookup().getBadResourceException(), unzippedDirRoot, itemRef.getHref()); } else { + ManifestMetadataBuilder metadata = getMetadataBuilder(itemRef); currentEditorCtrl = new AssessmentItemEditorController(ureq, getWindowControl(), testEntry, - item, itemRef, unzippedDirRoot); + item, itemRef, metadata, unzippedDirRoot); } } @@ -610,4 +602,12 @@ public class AssessmentTestComposerController extends MainLayoutBasicController mainVC.put("content", new Panel("empty")); } } + + private ManifestMetadataBuilder getMetadataBuilder(AssessmentItemRef itemRef) { + URI itemUri = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef).getItemLookup().getSystemId(); + File itemFile = new File(itemUri); + String relativePathToManifest = unzippedDirRoot.toPath().relativize(itemFile.toPath()).toString(); + ResourceType resource = manifestBuilder.getResourceTypeByHref(relativePathToManifest); + return manifestBuilder.getMetadataBuilder(resource, true); + } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/MetadataEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/MetadataEditorController.java new file mode 100644 index 0000000000000000000000000000000000000000..46f765e380fc717aef729272a0320ae455a18b6e --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/editor/MetadataEditorController.java @@ -0,0 +1,77 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti21.ui.editor; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder; + +/** + * + * Initial date: 23.02.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class MetadataEditorController extends FormBasicController { + + private TextElement titleEl, descriptionEl; + //keywords, language + + private final ManifestMetadataBuilder metadataBuilder; + + public MetadataEditorController(UserRequest ureq, WindowControl wControl, ManifestMetadataBuilder metadataBuilder) { + super(ureq, wControl); + this.metadataBuilder = metadataBuilder; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + String title = metadataBuilder.getTitle(); + titleEl = uifactory.addTextElement("title", "form.metadata.title", 256, title, formLayout); + + String description = metadataBuilder.getDescription(); + descriptionEl = uifactory.addTextAreaElement("description", "form.metadata.description", 4000, 4, 60, true, description, formLayout); + + // Submit Button + FormLayoutContainer buttonsContainer = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + buttonsContainer.setRootForm(mainForm); + formLayout.add(buttonsContainer); + uifactory.addFormSubmitButton("submit", buttonsContainer); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + metadataBuilder.setTitle(titleEl.getValue(), "en"); + metadataBuilder.setDescription(descriptionEl.getValue(), "en"); + fireEvent(ureq, Event.CHANGED_EVENT); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties index ea8bdde7f273d2cb4f511b7b9540d8a89aac1cda..484b32caac2f6e3da995bc12859fecc03bc650a7 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties @@ -26,6 +26,9 @@ form.imd.incorrect.title=Incorrect title form.imd.incorrect.text=Incorrect feedback form.imd.rubric=Rubric form.imd.shuffle=Shuffle +form.metadata=Metadata +form.metadata.title=Title +form.metadata.description=Description form.score=Score form.section.shuffle=Random order of questions? form.section.selection_pre=Number of questions in this section diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/events/AssessmentItemEvent.java b/src/main/java/org/olat/ims/qti21/ui/editor/events/AssessmentItemEvent.java index 3999384c8dda2a66cc20ba0f0516510c90db13b5..69d2a16c6a35a3c09e2f3d1923090bb81cfe9343 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/events/AssessmentItemEvent.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/events/AssessmentItemEvent.java @@ -36,6 +36,7 @@ public class AssessmentItemEvent extends Event { private static final long serialVersionUID = -1768118856227595311L; public static final String ASSESSMENT_ITEM_CHANGED = "assessment-item-changed"; + public static final String ASSESSMENT_ITEM_METADATA_CHANGED = "assessment-item-metadata-changed"; private AssessmentItem item; private AssessmentItemRef itemRef; diff --git a/src/main/java/org/olat/modules/qpool/QPoolSPI.java b/src/main/java/org/olat/modules/qpool/QPoolSPI.java index 7352397302c1d791185da8ccf0ea249791a37954..e87e5a72c7c2d5428679172dff4aeaa0e1018650 100644 --- a/src/main/java/org/olat/modules/qpool/QPoolSPI.java +++ b/src/main/java/org/olat/modules/qpool/QPoolSPI.java @@ -69,7 +69,7 @@ public interface QPoolSPI { * Export a test * @param items */ - public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format); + public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format, Locale locale); /** * Export the item to the Zip @@ -77,7 +77,7 @@ public interface QPoolSPI { * @param zout * @param names Collection of the file names used in the ZIP */ - public void exportItem(QuestionItemFull item, ZipOutputStream zout, Set<String> names); + public void exportItem(QuestionItemFull item, ZipOutputStream zout, Locale locale, Set<String> names); /** * Copy the item attachment... diff --git a/src/main/java/org/olat/modules/qpool/QPoolService.java b/src/main/java/org/olat/modules/qpool/QPoolService.java index 9155f49a6329de15559bc16abb75ea019086dcfe..1204ad9f9ad8c7e24183935c5eeede5c42a5fcc3 100644 --- a/src/main/java/org/olat/modules/qpool/QPoolService.java +++ b/src/main/java/org/olat/modules/qpool/QPoolService.java @@ -88,7 +88,7 @@ public interface QPoolService { public void removeAuthors(List<Identity> authors, List<QuestionItemShort> items); //import / export - public MediaResource export(List<QuestionItemShort> items, ExportFormatOptions format); + public MediaResource export(List<QuestionItemShort> items, ExportFormatOptions format, Locale locale); /** * @@ -96,7 +96,7 @@ public interface QPoolService { * @param zout * @param names Collection of the names used in the ZIP dd */ - public void exportItem(QuestionItemShort item, ZipOutputStream zout, Set<String> names); + public void exportItem(QuestionItemShort item, ZipOutputStream zout, Locale locale, Set<String> names); public List<QuestionItem> importItems(Identity owner, Locale defaultLocale, String filename, File file); diff --git a/src/main/java/org/olat/modules/qpool/manager/AbstractExportTestResource.java b/src/main/java/org/olat/modules/qpool/manager/AbstractExportTestResource.java index f85475edfde70ea8a1f6d2e210f098f03ab6ccf9..0123b493594dc98bd623998eba357f383a83e590 100644 --- a/src/main/java/org/olat/modules/qpool/manager/AbstractExportTestResource.java +++ b/src/main/java/org/olat/modules/qpool/manager/AbstractExportTestResource.java @@ -22,6 +22,7 @@ package org.olat.modules.qpool.manager; import java.io.IOException; import java.io.InputStream; import java.util.List; +import java.util.Locale; import java.util.zip.ZipOutputStream; import javax.servlet.http.HttpServletResponse; @@ -44,13 +45,19 @@ public abstract class AbstractExportTestResource implements MediaResource { private static final OLog log = Tracing.createLoggerFor(AbstractExportTestResource.class); private String encoding; + private final Locale locale; private final List<QuestionItemShort> items; - public AbstractExportTestResource(String encoding, List<QuestionItemShort> items) { + public AbstractExportTestResource(String encoding, Locale locale, List<QuestionItemShort> items) { this.encoding = encoding; + this.locale = locale; this.items = items; } + public Locale getLocale() { + return locale; + } + @Override public boolean acceptRanges() { return false; diff --git a/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java b/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java index 3f2ad1f257017e87ba0615adec39dc74b92ebbf3..9f19e2f9858a33a59d37e757db4127a7f11795fb 100644 --- a/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java +++ b/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java @@ -143,12 +143,12 @@ public abstract class AbstractQPoolServiceProvider implements QPoolSPI { } @Override - public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format) { + public MediaResource exportTest(List<QuestionItemShort> items, ExportFormatOptions format, Locale locale) { return null;//Zip are made by qpool service } @Override - public void exportItem(QuestionItemFull item, ZipOutputStream zout, Set<String> names) { + public void exportItem(QuestionItemFull item, ZipOutputStream zout, Locale locale, Set<String> names) { String directory = item.getDirectory(); VFSContainer itemDir = getFileStorage().getContainer(directory); VFSItem file = itemDir.resolve(item.getRootFilename()); diff --git a/src/main/java/org/olat/modules/qpool/manager/ExportQItemResource.java b/src/main/java/org/olat/modules/qpool/manager/ExportQItemResource.java index 6244b09f88db4b53d065433c316dc3e252093b16..ef45d167b85532fb12efa8731977f9ec98577d45 100644 --- a/src/main/java/org/olat/modules/qpool/manager/ExportQItemResource.java +++ b/src/main/java/org/olat/modules/qpool/manager/ExportQItemResource.java @@ -22,6 +22,7 @@ package org.olat.modules.qpool.manager; import java.io.IOException; import java.io.InputStream; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import java.util.zip.ZipOutputStream; @@ -47,10 +48,12 @@ public class ExportQItemResource implements MediaResource { private static final OLog log = Tracing.createLoggerFor(ExportQItemResource.class); private String encoding; + private final Locale locale; private final QuestionItemShort item; - public ExportQItemResource(String encoding, QuestionItemShort item) { + public ExportQItemResource(String encoding, Locale locale, QuestionItemShort item) { this.encoding = encoding; + this.locale = locale; this.item = item; } @@ -99,7 +102,7 @@ public class ExportQItemResource implements MediaResource { zout.setLevel(9); Set<String> names = new HashSet<String>(); QPoolService qpoolService = CoreSpringFactory.getImpl(QPoolService.class); - qpoolService.exportItem(item, zout, names); + qpoolService.exportItem(item, zout, locale, names); } catch (IOException e) { log.error("", e); } finally { diff --git a/src/main/java/org/olat/modules/qpool/manager/ExportQItemsZipResource.java b/src/main/java/org/olat/modules/qpool/manager/ExportQItemsZipResource.java index 6edd61b94e3f5473451ce829d2bb7f24f129fcd8..c0e802a53f7940fd351c549a5e1c70aa6e474b91 100644 --- a/src/main/java/org/olat/modules/qpool/manager/ExportQItemsZipResource.java +++ b/src/main/java/org/olat/modules/qpool/manager/ExportQItemsZipResource.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.zip.ZipOutputStream; @@ -48,10 +49,12 @@ public class ExportQItemsZipResource implements MediaResource { private static final OLog log = Tracing.createLoggerFor(ExportQItemsZipResource.class); private String encoding; + private final Locale locale; private final List<QuestionItemFull> items; - public ExportQItemsZipResource(String encoding, List<QuestionItemFull> items) { + public ExportQItemsZipResource(String encoding, Locale locale, List<QuestionItemFull> items) { this.encoding = encoding; + this.locale = locale; this.items = items; } @@ -101,7 +104,7 @@ public class ExportQItemsZipResource implements MediaResource { Set<String> names = new HashSet<String>(); QPoolService qpoolService = CoreSpringFactory.getImpl(QPoolService.class); for(QuestionItemFull item:items) { - qpoolService.exportItem(item, zout, names); + qpoolService.exportItem(item, zout, locale, names); } } catch (IOException e) { log.error("", e); diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java index c7b7e89b084e117ae48c3432e72234af1ab73b52..14f21a8fc3a791fc5ee6acd9ad9afa5e9a370b5f 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java @@ -223,12 +223,12 @@ public class QuestionPoolServiceImpl implements QPoolService { } @Override - public MediaResource export(List<QuestionItemShort> items, ExportFormatOptions format) { + public MediaResource export(List<QuestionItemShort> items, ExportFormatOptions format, Locale locale) { MediaResource mr = null; if(DefaultExportFormat.ZIP_EXPORT_FORMAT.equals(format)) { List<Long> keys = toKeys(items); List<QuestionItemFull> fullItems = questionItemDao.loadByIds(keys); - mr = new ExportQItemsZipResource("UTF-8", fullItems); + mr = new ExportQItemsZipResource("UTF-8", locale, fullItems); //make a zip with all items } else { QPoolSPI selectedSp = null; @@ -241,7 +241,7 @@ public class QuestionPoolServiceImpl implements QPoolService { } if(selectedSp != null) { - mr = selectedSp.exportTest(items, format); + mr = selectedSp.exportTest(items, format, locale); } } return mr; @@ -257,7 +257,7 @@ public class QuestionPoolServiceImpl implements QPoolService { } @Override - public void exportItem(QuestionItemShort item, ZipOutputStream zout, Set<String> names) { + public void exportItem(QuestionItemShort item, ZipOutputStream zout, Locale locale, Set<String> names) { QPoolSPI provider = qpoolModule.getQuestionPoolProvider(item.getFormat()); if(provider == null) { log.error("Not found provider for this format: " + item.getFormat()); @@ -268,7 +268,7 @@ public class QuestionPoolServiceImpl implements QPoolService { } else { fullItem = questionItemDao.loadById(item.getKey()); } - provider.exportItem(fullItem, zout, names); + provider.exportItem(fullItem, zout, locale, names); } } diff --git a/src/main/java/org/olat/modules/qpool/ui/QuestionItemDetailsController.java b/src/main/java/org/olat/modules/qpool/ui/QuestionItemDetailsController.java index fb0e3ba3f86c0e05ce4c11c53b91cd6757fdd982..db211d312dbc3db33ddfa9f77e3ac805c0bc61bc 100644 --- a/src/main/java/org/olat/modules/qpool/ui/QuestionItemDetailsController.java +++ b/src/main/java/org/olat/modules/qpool/ui/QuestionItemDetailsController.java @@ -280,7 +280,7 @@ public class QuestionItemDetailsController extends BasicController implements Br } private void doExport(UserRequest ureq, QuestionItemShort item) { - ExportQItemResource mr = new ExportQItemResource("UTF-8", item); + ExportQItemResource mr = new ExportQItemResource("UTF-8", getLocale(), item); ureq.getDispatchResult().setResultingMediaResource(mr); } } diff --git a/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java b/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java index 6f784913e34d0943b09d84a4f689aeb332a09c9e..63724a4e44c87c660229f462315a278dcddee953 100644 --- a/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java +++ b/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java @@ -741,7 +741,7 @@ public class QuestionListController extends AbstractItemListController implement List<QuestionItemShort> items = (List<QuestionItemShort>)runContext.get("itemsToExport"); switch(format.getOutcome()) { case download: { - MediaResource mr = qpoolService.export(items, format); + MediaResource mr = qpoolService.export(items, format, getLocale()); if(mr != null) { ureq.getDispatchResult().setResultingMediaResource(mr); } diff --git a/src/test/java/org/olat/ims/qti21/pool/QTI12To21ConverterTest.java b/src/test/java/org/olat/ims/qti21/pool/QTI12To21ConverterTest.java index bc7a84788060d3e5fafe7c384a9df9d470e26f1c..f8887b9968f73d12e4f23f16b1134a3569e31c6d 100644 --- a/src/test/java/org/olat/ims/qti21/pool/QTI12To21ConverterTest.java +++ b/src/test/java/org/olat/ims/qti21/pool/QTI12To21ConverterTest.java @@ -22,6 +22,7 @@ package org.olat.ims.qti21.pool; import java.io.File; import java.io.InputStream; import java.net.URISyntaxException; +import java.util.Locale; import org.dom4j.Document; import org.junit.Test; @@ -47,7 +48,7 @@ public class QTI12To21ConverterTest { QTIDocument doc = loadDocument("qti12_4questiontypes.xml"); File exportDir = new File("/HotCoffee/QTI/today/"); exportDir.mkdirs(); - QTI12To21Converter converter = new QTI12To21Converter(exportDir); + QTI12To21Converter converter = new QTI12To21Converter(exportDir, Locale.ENGLISH); converter.convert(doc);