diff --git a/src/main/java/org/olat/core/util/cache/CacheWrapper.java b/src/main/java/org/olat/core/util/cache/CacheWrapper.java index db8ef6a985fd6b6526949491d00ceda342f6ea5d..4a3ac8114618bf0d11ff9054a12fc0350f8236d0 100644 --- a/src/main/java/org/olat/core/util/cache/CacheWrapper.java +++ b/src/main/java/org/olat/core/util/cache/CacheWrapper.java @@ -27,6 +27,7 @@ package org.olat.core.util.cache; import java.util.Iterator; import java.util.List; +import java.util.function.Function; /** @@ -91,6 +92,8 @@ public interface CacheWrapper<U, V> { public V putIfAbsent(U key, V value); + public V computeIfAbsent(U key, Function<? super U, ? extends V> mappingFunction); + /** * In the case of distributed cache, the list can be partial and * you must carefully setup your cache. diff --git a/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacheWrapper.java b/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacheWrapper.java index 7fd5c87768806cf67d76cfd721ed0a8d7b280191..6dcdc4fe1288084f8cab7ec8039dcb73216af0df 100644 --- a/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacheWrapper.java +++ b/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacheWrapper.java @@ -28,6 +28,7 @@ package org.olat.core.util.cache.infinispan; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import org.infinispan.Cache; import org.olat.core.logging.OLATRuntimeException; @@ -108,6 +109,11 @@ public class InfinispanCacheWrapper<U,V> implements CacheWrapper<U,V> { return cache.putIfAbsent(key, value); } + @Override + public V computeIfAbsent(U key, Function<? super U, ? extends V> mappingFunction) { + return cache.computeIfAbsent(key, mappingFunction); + } + @Override public Iterator<U> iterateKeys() { return cache.keySet().iterator(); diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java index 97481cdb8a1870ced395cb013db2c02063b5150b..ccc421389770c4938a4613aee8e4f9671d559234 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java @@ -33,6 +33,8 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.xml.parsers.DocumentBuilder; import javax.xml.transform.Transformer; @@ -146,11 +148,15 @@ public class QTI21ServiceImpl implements QTI21Service, InitializingBean, Disposa private QTI21Module qtiModule; @Autowired private CoordinatorManager coordinatorManager; + private JqtiExtensionManager jqtiExtensionManager; private XsltStylesheetManager xsltStylesheetManager; private InfinispanXsltStylesheetCache xsltStylesheetCache; - private CacheWrapper<File,ResolvedAssessmentObject<?>> assessmentTestsAndItemsCache; + private CacheWrapper<File,ResolvedAssessmentTest> assessmentTestsCache; + private CacheWrapper<File,ResolvedAssessmentItem> assessmentItemsCache; + + private final ConcurrentMap<String,URI> resourceToTestURI = new ConcurrentHashMap<>(); @Autowired public QTI21ServiceImpl(InfinispanXsltStylesheetCache xsltStylesheetCache) { @@ -171,7 +177,8 @@ public class QTI21ServiceImpl implements QTI21Service, InitializingBean, Disposa jqtiExtensionManager.init(); - assessmentTestsAndItemsCache = coordinatorManager.getInstance().getCoordinator().getCacher().getCache("QTIWorks", "assessmentTestsAndItems"); + assessmentTestsCache = coordinatorManager.getInstance().getCoordinator().getCacher().getCache("QTIWorks", "assessmentTests"); + assessmentItemsCache = coordinatorManager.getInstance().getCoordinator().getCacher().getCache("QTIWorks", "assessmentItems"); } @Override @@ -243,44 +250,30 @@ public class QTI21ServiceImpl implements QTI21Service, InitializingBean, Disposa @Override public ResolvedAssessmentTest loadAndResolveAssessmentTest(File resourceDirectory) { - ResolvedAssessmentTest result = (ResolvedAssessmentTest)assessmentTestsAndItemsCache.get(resourceDirectory); - if(result == null) { + URI assessmentObjectSystemId = createAssessmentObjectUri(resourceDirectory); + File resourceFile = new File(assessmentObjectSystemId); + return assessmentTestsCache.computeIfAbsent(resourceFile, file -> { QtiXmlReader qtiXmlReader = new QtiXmlReader(jqtiExtensionManager()); ResourceLocator fileResourceLocator = new PathResourceLocator(resourceDirectory.toPath()); ResourceLocator inputResourceLocator = ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator); - URI assessmentObjectSystemId = createAssessmentObjectUri(resourceDirectory); AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, inputResourceLocator); - result = assessmentObjectXmlLoader.loadAndResolveAssessmentTest(assessmentObjectSystemId); - - File resourceFile = new File(assessmentObjectSystemId); - ResolvedAssessmentTest cachedResult = (ResolvedAssessmentTest)assessmentTestsAndItemsCache.putIfAbsent(resourceFile, result); - if(cachedResult != null) { - result = cachedResult; - } - } - return result; + return assessmentObjectXmlLoader.loadAndResolveAssessmentTest(assessmentObjectSystemId); + }); } @Override public ResolvedAssessmentItem loadAndResolveAssessmentItem(URI assessmentObjectSystemId, File resourceDirectory) { File resourceFile = new File(assessmentObjectSystemId); - ResolvedAssessmentItem result = (ResolvedAssessmentItem)assessmentTestsAndItemsCache.get(resourceFile); - if(result == null) { + return assessmentItemsCache.computeIfAbsent(resourceFile, (file) -> { QtiXmlReader qtiXmlReader = new QtiXmlReader(jqtiExtensionManager()); ResourceLocator fileResourceLocator = new PathResourceLocator(resourceDirectory.toPath()); ResourceLocator inputResourceLocator = ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator); AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, inputResourceLocator); - result = assessmentObjectXmlLoader.loadAndResolveAssessmentItem(assessmentObjectSystemId); - - ResolvedAssessmentItem cachedResult = (ResolvedAssessmentItem)assessmentTestsAndItemsCache.putIfAbsent(resourceFile, result); - if(cachedResult != null) { - result = cachedResult; - } - } - return result; + return assessmentObjectXmlLoader.loadAndResolveAssessmentItem(assessmentObjectSystemId); + }); } @Override @@ -304,7 +297,8 @@ public class QTI21ServiceImpl implements QTI21Service, InitializingBean, Disposa try(FileOutputStream out = new FileOutputStream(resourceFile)) { qtiSerializer().serializeJqtiObject(assessmentObject, out); //TODO qti - assessmentTestsAndItemsCache.remove(resourceFile); + assessmentTestsCache.remove(resourceFile); + assessmentItemsCache.remove(resourceFile); return true; } catch(Exception e) { log.error("", e); @@ -313,16 +307,19 @@ public class QTI21ServiceImpl implements QTI21Service, InitializingBean, Disposa } @Override - public URI createAssessmentObjectUri(File resourceDirectory) { - File manifestPath = new File(resourceDirectory, "imsmanifest.xml"); - QTI21ContentPackage cp = new QTI21ContentPackage(manifestPath.toPath()); - try { - Path testPath = cp.getTest(); - return testPath.toUri(); - } catch (IOException e) { - log.error("", e); - } - return null; + public URI createAssessmentObjectUri(final File resourceDirectory) { + final String key = resourceDirectory.getAbsolutePath(); + return resourceToTestURI.computeIfAbsent(key, (directoryAbsolutPath) -> { + File manifestPath = new File(resourceDirectory, "imsmanifest.xml"); + QTI21ContentPackage cp = new QTI21ContentPackage(manifestPath.toPath()); + try { + Path testPath = cp.getTest(); + return testPath.toUri(); + } catch (IOException e) { + log.error("", e); + return null; + } + }); } @Override 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 15fa1860c9b519a5a4479cec03c8f6744c7121a3..45b8fea64d01c34fbd9c38ec629d8d26374b9c67 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 @@ -64,6 +64,7 @@ import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ResourceEvaluation; 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.AssessmentItemFactory; import org.olat.ims.qti21.model.xml.AssessmentTestFactory; import org.olat.ims.qti21.model.xml.ManifestPackage; @@ -146,13 +147,13 @@ public class QTI21AssessmentTestHandler extends FileHandler { QTI21Service qti21Service = CoreSpringFactory.getImpl(QTI21Service.class); //single choice - File itemFile = new File(directory, IdentifierGenerator.newAssessmentTestFilename()); + File itemFile = new File(directory, IdentifierGenerator.newAsString(QTI21QuestionType.sc.getPrefix())); AssessmentItem assessmentItem = AssessmentItemFactory.createSingleChoice(); QtiSerializer qtiSerializer = qti21Service.qtiSerializer(); ManifestPackage.appendAssessmentItem(itemFile.getName(), manifestType); //test - File testFile = new File(directory, IdentifierGenerator.newAssessmentItemFilename()); + File testFile = new File(directory, IdentifierGenerator.newAssessmentTestFilename()); AssessmentTest assessmentTest = AssessmentTestFactory.createAssessmentTest(displayName); ManifestPackage.appendAssessmentTest(testFile.getName(), manifestType); diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_11_0_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_11_0_0.java index 10002985fe28b9baffa36b33fa63f78b39683603..db3e5fa74a8f4df5b2581a7ad626d4929e8a8cf1 100644 --- a/src/main/java/org/olat/upgrade/OLATUpgrade_11_0_0.java +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_11_0_0.java @@ -41,6 +41,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import org.olat.admin.user.imp.TransientIdentity; import org.olat.basesecurity.GroupRoles; @@ -1098,6 +1099,12 @@ public class OLATUpgrade_11_0_0 extends OLATUpgrade { return map.putIfAbsent(key, value); } + @Override + public HashMap<String, Serializable> computeIfAbsent(NewCacheKey key, + Function<? super NewCacheKey, ? extends HashMap<String, Serializable>> mappingFunction) { + return map.computeIfAbsent(key, mappingFunction); + } + @Override public List<NewCacheKey> getKeys() { return new ArrayList<>(map.keySet()); diff --git a/src/test/java/org/olat/gatling/BigAssessmentTestPackageBuilder.java b/src/test/java/org/olat/gatling/BigAssessmentTestPackageBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..997429c50fd72ef05196b51052668210db1c0140 --- /dev/null +++ b/src/test/java/org/olat/gatling/BigAssessmentTestPackageBuilder.java @@ -0,0 +1,140 @@ +/** + * <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.gatling; + +import java.io.File; +import java.io.FileOutputStream; +import java.net.URISyntaxException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; + +import org.junit.Test; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.ims.qti21.model.IdentifierGenerator; +import org.olat.ims.qti21.model.QTI21QuestionType; +import org.olat.ims.qti21.model.xml.AssessmentItemFactory; +import org.olat.ims.qti21.model.xml.AssessmentTestFactory; +import org.olat.ims.qti21.model.xml.ManifestPackage; +import org.olat.imscp.xml.manifest.ManifestType; + +import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager; +import uk.ac.ed.ph.jqtiplus.node.content.variable.RubricBlock; +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection; +import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest; +import uk.ac.ed.ph.jqtiplus.node.test.ItemSessionControl; +import uk.ac.ed.ph.jqtiplus.node.test.Ordering; +import uk.ac.ed.ph.jqtiplus.node.test.Selection; +import uk.ac.ed.ph.jqtiplus.node.test.TestPart; +import uk.ac.ed.ph.jqtiplus.node.test.View; +import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer; + +/** + * + * Initial date: 04.06.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BigAssessmentTestPackageBuilder { + + private static final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-HHmmss"); + private static final OLog log = Tracing.createLoggerFor(BigAssessmentTestPackageBuilder.class); + private static final QtiSerializer qtiSerializer = new QtiSerializer(new JqtiExtensionManager()); + + private int numOfSections = 15; + private int numOfQuestions = 500; + + @Test + public void createAssessmentTest() throws URISyntaxException { + String date = format.format(new Date()); + File directory = new File("/HotCoffee/qti/" + date + "/"); + directory.mkdirs(); + ManifestType manifestType = ManifestPackage.createEmptyManifest(); + System.out.println(directory); + + + //test + File testFile = new File(directory, IdentifierGenerator.newAssessmentTestFilename()); + AssessmentTest assessmentTest = AssessmentTestFactory.createAssessmentTest("Big test " + date); + ManifestPackage.appendAssessmentTest(testFile.getName(), manifestType); + + TestPart part = assessmentTest.getTestParts().get(0); + part.getAssessmentSections().clear(); + + // section + for(int i=0; i<numOfSections; i++) { + AssessmentSection section = new AssessmentSection(part); + section.setFixed(Boolean.TRUE); + section.setVisible(Boolean.TRUE); + section.setTitle((i+1) + ". Section"); + section.setIdentifier(IdentifierGenerator.newAsIdentifier("sec")); + part.getAssessmentSections().add(section); + + Ordering ordering = new Ordering(section); + ordering.setShuffle(true); + section.setOrdering(ordering); + + Selection selection = new Selection(section); + selection.setSelect(4); + section.setSelection(selection); + + ItemSessionControl itemSessionControl = new ItemSessionControl(section); + itemSessionControl.setAllowSkipping(Boolean.TRUE); + itemSessionControl.setAllowComment(Boolean.FALSE); + itemSessionControl.setShowFeedback(Boolean.FALSE); + section.setItemSessionControl(itemSessionControl); + + RubricBlock rubrickBlock = new RubricBlock(section); + rubrickBlock.setViews(Collections.singletonList(View.CANDIDATE)); + section.getRubricBlocks().add(rubrickBlock); + + for(int j=0; j<numOfQuestions; j++) { + //single choice + String itemId = IdentifierGenerator.newAsString(QTI21QuestionType.sc.getPrefix()); + File itemFile = new File(directory, itemId + ".xml"); + AssessmentItem assessmentItem = AssessmentItemFactory.createSingleChoice(); + assessmentItem.setTitle((i+1) + "." + (j+1) + ". Question SC"); + + AssessmentTestFactory.appendAssessmentItem(section, itemFile.getName()); + ManifestPackage.appendAssessmentItem(itemFile.getName(), manifestType); + + try(FileOutputStream out = new FileOutputStream(itemFile)) { + qtiSerializer.serializeJqtiObject(assessmentItem, out); + } catch(Exception e) { + log.error("", e); + } + } + } + + try(FileOutputStream out = new FileOutputStream(testFile)) { + qtiSerializer.serializeJqtiObject(assessmentTest, out); + } catch(Exception e) { + log.error("", e); + } + + try(FileOutputStream out = new FileOutputStream(new File(directory, "imsmanifest.xml"))) { + ManifestPackage.write(manifestType, out); + } catch(Exception e) { + log.error("", e); + } + } +} \ No newline at end of file