From 71ebb3a7db44a5f121b704fc1f37c676a16de6a2 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 2 Oct 2015 08:29:08 +0200
Subject: [PATCH] OO-1635: make the code more efficient, use cache for
 stylesheet and MathML...

---
 .../olat/course/nodes/IQTESTCourseNode.java   |   2 +-
 .../iq/QTI21AssessmentRunController.java      |   4 +-
 .../java/org/olat/ims/qti21/QTI21Service.java |  27 ++++
 .../ims/qti21/manager/OnyxToQTIWorks.java     |  34 -----
 .../ims/qti21/manager/QTI21ServiceImpl.java   | 102 ++++++++++-----
 .../handlers/QTI21AssessmentTestHandler.java  |   6 +-
 .../ims/qti21/restapi/MathWebService.java     |   6 +-
 .../ui/AssessmentItemDisplayController.java   |  56 +++------
 .../ui/AssessmentTestDisplayController.java   | 119 +++++++-----------
 .../AssessmentObjectComponentRenderer.java    |   6 +-
 .../AssessmentTestComponentRenderer.java      |   7 +-
 11 files changed, 183 insertions(+), 186 deletions(-)
 delete mode 100644 src/main/java/org/olat/ims/qti21/manager/OnyxToQTIWorks.java

diff --git a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
index cd474f2ffda..c60c2ef5e4c 100644
--- a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
@@ -437,7 +437,7 @@ public class IQTESTCourseNode extends AbstractAccessableCourseNode implements As
 		AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
 		Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity();
 		try {
-		 am.saveScoreEvaluation(this, coachingIdentity, mySelf, scoreEvaluation, userCourseEnvironment, incrementAttempts);
+			am.saveScoreEvaluation(this, coachingIdentity, mySelf, scoreEvaluation, userCourseEnvironment, incrementAttempts);
 		} catch(DBRuntimeException ex) {
 			throw new KnownIssueException("DBRuntimeException - Row was updated or deleted...", 3570, ex);
 		}
diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
index 87fd076e5c5..172b3c92ae4 100644
--- a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
+++ b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
@@ -282,8 +282,8 @@ public class QTI21AssessmentRunController extends BasicController implements Gen
 
 	@Override
 	public void updateOutcomes(Float score, Boolean pass) {
-		ScoreEvaluation sceval = new ScoreEvaluation(score, pass, Boolean.FALSE);
-		courseNode.updateUserScoreEvaluation(sceval, userCourseEnv, getIdentity(), false);
+		//ScoreEvaluation sceval = new ScoreEvaluation(score, pass, Boolean.FALSE);
+		//courseNode.updateUserScoreEvaluation(sceval, userCourseEnv, getIdentity(), false);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java
index 5970e910f21..d0d60d7c5d6 100644
--- a/src/main/java/org/olat/ims/qti21/QTI21Service.java
+++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java
@@ -34,12 +34,16 @@ import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 
+import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
 import uk.ac.ed.ph.jqtiplus.node.result.AssessmentResult;
 import uk.ac.ed.ph.jqtiplus.notification.NotificationRecorder;
 import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentObject;
+import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
 import uk.ac.ed.ph.jqtiplus.state.ItemSessionState;
 import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey;
 import uk.ac.ed.ph.jqtiplus.state.TestSessionState;
+import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetCache;
+import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetManager;
 
 /**
  * 
@@ -49,6 +53,29 @@ import uk.ac.ed.ph.jqtiplus.state.TestSessionState;
  */
 public interface QTI21Service {
 	
+	/**
+	 * New QTI serializer
+	 * @return
+	 */
+	public QtiSerializer qtiSerializer();
+	
+	/**
+	 * The manager for custom extensions to QTI (MathExtensio )
+	 * @return
+	 */
+	public JqtiExtensionManager jqtiExtensionManager();
+	
+	/**
+	 * @return The cache for stylesheets used by MathML transformation
+	 */
+	public XsltStylesheetCache getXsltStylesheetCache();
+	
+	/**
+	 * @return The stylesheets manager used by MathML transformation
+	 */
+	public XsltStylesheetManager getXsltStylesheetManager();
+	
+	
 	public URI createAssessmentObjectUri(File resourceDirectory);
 	
 	public <E extends ResolvedAssessmentObject<?>> E loadAndResolveAssessmentObject(File resourceDirectory);
diff --git a/src/main/java/org/olat/ims/qti21/manager/OnyxToQTIWorks.java b/src/main/java/org/olat/ims/qti21/manager/OnyxToQTIWorks.java
deleted file mode 100644
index f5dc1e331bb..00000000000
--- a/src/main/java/org/olat/ims/qti21/manager/OnyxToQTIWorks.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * <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.manager;
-
-/**
- * 
- * 
- *  //TODO <rubricBlock><div></div></rubricBlock>: add a div
- *  //TODO <prompt><p></p></prompt>: remove the p's
- * 
- * Initial date: 11.05.2015<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class OnyxToQTIWorks {
-
-}
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 d761e4f3d5f..b89578b7712 100644
--- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
+++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
@@ -44,6 +44,8 @@ import org.olat.core.logging.OLATRuntimeException;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.FileUtils;
+import org.olat.core.util.cache.CacheWrapper;
+import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.fileresource.types.ImsQTI21Resource;
 import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator;
 import org.olat.ims.qti21.QTI21Constants;
@@ -58,8 +60,9 @@ import org.olat.ims.qti21.ui.rendering.XmlUtilities;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
 import org.springframework.stereotype.Service;
 import org.w3c.dom.Document;
 
@@ -88,8 +91,10 @@ import uk.ac.ed.ph.jqtiplus.value.NumberValue;
 import uk.ac.ed.ph.jqtiplus.value.RecordValue;
 import uk.ac.ed.ph.jqtiplus.value.SingleValue;
 import uk.ac.ed.ph.jqtiplus.value.Value;
+import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ClassPathResourceLocator;
 import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator;
 import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltSerializationOptions;
+import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetCache;
 import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetManager;
 import uk.ac.ed.ph.qtiworks.mathassess.GlueValueBinder;
 import uk.ac.ed.ph.qtiworks.mathassess.MathAssessConstants;
@@ -102,7 +107,7 @@ import uk.ac.ed.ph.qtiworks.mathassess.MathAssessExtensionPackage;
  *
  */
 @Service
-public class QTI21ServiceImpl implements QTI21Service {
+public class QTI21ServiceImpl implements QTI21Service, InitializingBean, DisposableBean {
 	
 	private static final OLog log = Tracing.createLoggerFor(QTI21ServiceImpl.class);
 	
@@ -114,53 +119,93 @@ public class QTI21ServiceImpl implements QTI21Service {
 	private QTI21Storage storage;
 	@Autowired
 	private QTI21Module qtiModule;
-	
+	@Autowired
+	private CoordinatorManager coordinatorManager;
+
+	private JqtiExtensionManager jqtiExtensionManager;
+	private XsltStylesheetManager xsltStylesheetManager;
 	private InfinispanXsltStylesheetCache xsltStylesheetCache;
+	private CacheWrapper<File,ResolvedAssessmentObject<?>> assessmentTestsAndItemsCache;
 	
 	@Autowired
 	public QTI21ServiceImpl(InfinispanXsltStylesheetCache xsltStylesheetCache) {
 		this.xsltStylesheetCache = xsltStylesheetCache;
 	}
-	
-    @Bean(initMethod="init", destroyMethod="destroy")
-    public JqtiExtensionManager jqtiExtensionManager() {
-        final List<JqtiExtensionPackage<?>> extensionPackages = new ArrayList<JqtiExtensionPackage<?>>();
+
+    @Override
+	public void afterPropertiesSet() throws Exception {
+    	final List<JqtiExtensionPackage<?>> extensionPackages = new ArrayList<JqtiExtensionPackage<?>>();
 
         /* Enable MathAssess extensions if requested */
         if (qtiModule.isMathAssessExtensionEnabled()) {
             log.info("Enabling the MathAssess extensions");
             extensionPackages.add(new MathAssessExtensionPackage(xsltStylesheetCache));
         }
+        jqtiExtensionManager = new JqtiExtensionManager(extensionPackages);
+        xsltStylesheetManager = new XsltStylesheetManager(new ClassPathResourceLocator(), xsltStylesheetCache);
+        
+        jqtiExtensionManager.init();
+
+        assessmentTestsAndItemsCache = coordinatorManager.getInstance().getCoordinator().getCacher().getCache("QTIWorks", "assessmentTestsAndItems");
+	}
+
+    @Override
+	public void destroy() throws Exception {
+		if(jqtiExtensionManager != null) {
+			jqtiExtensionManager.destroy();
+		}
+	}
+
+	@Override
+	public XsltStylesheetCache getXsltStylesheetCache() {
+		return xsltStylesheetCache;
+	}
 
-        return new JqtiExtensionManager(extensionPackages);
+	@Override
+    public XsltStylesheetManager getXsltStylesheetManager() {
+    	return xsltStylesheetManager;
+    }
+
+    @Override
+    public JqtiExtensionManager jqtiExtensionManager() {
+        return jqtiExtensionManager;
     }
     
-    @Bean
+    @Override
     public QtiSerializer qtiSerializer() {
         return new QtiSerializer(jqtiExtensionManager());
     }
+    
+    
 	
 	@SuppressWarnings("unchecked")
 	@Override
-	public <E extends ResolvedAssessmentObject<?>> E loadAndResolveAssessmentObject(File resourceDirectory) {
-		QtiXmlReader qtiXmlReader = new QtiXmlReader(jqtiExtensionManager());
-        
-		ResourceLocator fileResourceLocator = new PathResourceLocator(resourceDirectory.toPath());
-		final ResourceLocator inputResourceLocator = 
-        		ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator);
-        final URI assessmentObjectSystemId = createAssessmentObjectUri(resourceDirectory);
-        final AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, inputResourceLocator);
-        final AssessmentObjectType assessmentObjectType = AssessmentObjectType.ASSESSMENT_TEST;
-        E result;
-        if (assessmentObjectType==AssessmentObjectType.ASSESSMENT_ITEM) {
-            result = (E) assessmentObjectXmlLoader.loadAndResolveAssessmentItem(assessmentObjectSystemId);
-        }
-        else if (assessmentObjectType==AssessmentObjectType.ASSESSMENT_TEST) {
-            result = (E) assessmentObjectXmlLoader.loadAndResolveAssessmentTest(assessmentObjectSystemId);
-        }
-        else {
-            throw new OLATRuntimeException("Unexpected branch " + assessmentObjectType, null);
-        }
+	public ResolvedAssessmentObject<?> loadAndResolveAssessmentObject(File resourceDirectory) {
+		ResolvedAssessmentObject<?> result;
+		if(assessmentTestsAndItemsCache.containsKey(resourceDirectory)) {
+			return assessmentTestsAndItemsCache.get(resourceDirectory);
+		} else {
+			QtiXmlReader qtiXmlReader = new QtiXmlReader(jqtiExtensionManager());
+			ResourceLocator fileResourceLocator = new PathResourceLocator(resourceDirectory.toPath());
+			final ResourceLocator inputResourceLocator = 
+	        		ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator);
+	        final URI assessmentObjectSystemId = createAssessmentObjectUri(resourceDirectory);
+	        final AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, inputResourceLocator);
+	        final AssessmentObjectType assessmentObjectType = AssessmentObjectType.ASSESSMENT_TEST;
+	        
+	        if (assessmentObjectType==AssessmentObjectType.ASSESSMENT_ITEM) {
+	            result = assessmentObjectXmlLoader.loadAndResolveAssessmentItem(assessmentObjectSystemId);
+	        } else if (assessmentObjectType==AssessmentObjectType.ASSESSMENT_TEST) {
+	            result = assessmentObjectXmlLoader.loadAndResolveAssessmentTest(assessmentObjectSystemId);
+	        } else {
+	            throw new OLATRuntimeException("Unexpected branch " + assessmentObjectType, null);
+	        }
+	        
+	        ResolvedAssessmentObject<?> cachedResult = assessmentTestsAndItemsCache.putIfAbsent(resourceDirectory, result);
+	        if(cachedResult != null) {
+	        	result = cachedResult;
+	        }
+		}
         return result;
 	}
 
@@ -386,7 +431,6 @@ public class QTI21ServiceImpl implements QTI21Service {
         }
     }
 
-
 	@Override
 	public UserTestSession finishItemSession(UserTestSession candidateSession, AssessmentResult assessmentResult, Date timestamp) {
 		/* Mark session as finished */
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 b1d5c40c88a..3ca9f5b5e07 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
@@ -62,6 +62,7 @@ import org.olat.fileresource.ZippedDirectoryMediaResource;
 import org.olat.fileresource.types.FileResource;
 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.xml.AssessmentItemFactory;
 import org.olat.ims.qti21.model.xml.AssessmentTestFactory;
@@ -83,7 +84,6 @@ import org.olat.resource.OLATResource;
 import org.olat.resource.OLATResourceManager;
 import org.springframework.stereotype.Service;
 
-import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
 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;
@@ -134,11 +134,13 @@ public class QTI21AssessmentTestHandler extends FileHandler {
 	
 	public void createMinimalAssessmentTest(String displayName, File directory) {
         ManifestType manifestType = ManifestPackage.createEmptyManifest();
+        
+        QTI21Service qti21Service = CoreSpringFactory.getImpl(QTI21Service.class);
 
 		//single choice
 		File itemFile = new File(directory, IdentifierGenerator.newAssessmentTestFilename());
 		AssessmentItem assessmentItem = AssessmentItemFactory.createSingleChoice();
-		QtiSerializer qtiSerializer = new QtiSerializer(new JqtiExtensionManager());
+		QtiSerializer qtiSerializer = qti21Service.qtiSerializer();
 		ManifestPackage.appendAssessmentItem(itemFile.getName(), manifestType);	
 		
 		//test
diff --git a/src/main/java/org/olat/ims/qti21/restapi/MathWebService.java b/src/main/java/org/olat/ims/qti21/restapi/MathWebService.java
index a1432cb8725..ecebee4e7d8 100644
--- a/src/main/java/org/olat/ims/qti21/restapi/MathWebService.java
+++ b/src/main/java/org/olat/ims/qti21/restapi/MathWebService.java
@@ -30,7 +30,9 @@ import javax.ws.rs.core.CacheControl;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
-import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.SimpleXsltStylesheetCache;
+import org.olat.core.CoreSpringFactory;
+import org.olat.ims.qti21.QTI21Service;
+
 import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetCache;
 import uk.ac.ed.ph.qtiworks.mathassess.XsltStylesheetCacheAdapter;
 import uk.ac.ed.ph.qtiworks.mathassess.glue.AsciiMathHelper;
@@ -55,7 +57,7 @@ public class MathWebService {
     @Path("verifyAsciiMath")
     @Produces({MediaType.APPLICATION_JSON})
     public Response verifyAsciiMath(@FormParam("input") String asciiMathInput) {
-    	XsltStylesheetCache stylesheetCache = new SimpleXsltStylesheetCache();
+    	XsltStylesheetCache stylesheetCache = CoreSpringFactory.getImpl(QTI21Service.class).getXsltStylesheetCache();
     	AsciiMathHelper asciiMathHelper = new AsciiMathHelper(new XsltStylesheetCacheAdapter(stylesheetCache));
         Map<String, String> upConvertedAsciiMathInput = asciiMathHelper.upConvertAsciiMathInput(asciiMathInput);
         return Response.ok(upConvertedAsciiMathInput).lastModified(new Date()).cacheControl(cc).build();
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
index 0e6d4763b29..15d18d08205 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
@@ -52,7 +52,6 @@ import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
 
-import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
 import uk.ac.ed.ph.jqtiplus.JqtiPlus;
 import uk.ac.ed.ph.jqtiplus.exception.QtiCandidateStateException;
 import uk.ac.ed.ph.jqtiplus.node.result.AssessmentResult;
@@ -95,8 +94,6 @@ public class AssessmentItemDisplayController extends BasicController implements
 
 	@Autowired
 	private QTI21Service qtiService;
-	@Autowired
-	private JqtiExtensionManager jqtiExtensionManager;
 	
 	public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl,
 			RepositoryEntry testEntry, AssessmentEntry assessmentEntry, boolean authorMode,
@@ -171,10 +168,6 @@ public class AssessmentItemDisplayController extends BasicController implements
 		}
 		super.event(ureq, source, event);
 	}
-
-	protected CandidateEvent assertSessionEntered(UserTestSession candidateSession) {
-		return lastEvent;
-	}
 	
 	private void processQTIEvent(UserRequest ureq, QTIWorksAssessmentItemEvent qe) {
 		currentRequestTimestamp = ureq.getRequestTimestamp();
@@ -219,7 +212,7 @@ public class AssessmentItemDisplayController extends BasicController implements
         final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
 
         /* Create fresh JQTI+ state Object and try to create controller */
-        final ItemSessionController itemSessionController = createNewItemSessionStateAndController(notificationRecorder);
+        itemSessionController = createNewItemSessionStateAndController(notificationRecorder);
         if (itemSessionController==null) {
         	logError("", null);
             return null;//handleExplosion(null, candidateSession);
@@ -244,7 +237,7 @@ public class AssessmentItemDisplayController extends BasicController implements
         lastEvent = candidateEvent;
 
         /* Record current result state */
-        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq, candidateSession, itemSessionController);
+        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
 
         /* Handle immediate end of session */
         if (itemSessionState.isEnded()) {
@@ -270,7 +263,7 @@ public class AssessmentItemDisplayController extends BasicController implements
         itemSessionControllerSettings.setMaxAttempts(10 /*itemDeliverySettings.getMaxAttempts() */);
 
         /* Create controller and wire up notification recorder */
-        final ItemSessionController result = new ItemSessionController(jqtiExtensionManager,
+        final ItemSessionController result = new ItemSessionController(qtiService.jqtiExtensionManager(),
                 itemSessionControllerSettings, itemProcessingMap, itemSessionState);
         if (notificationRecorder != null) {
             result.addNotificationListener(notificationRecorder);
@@ -302,10 +295,9 @@ public class AssessmentItemDisplayController extends BasicController implements
 		// assertSessionNotTerminated(candidateSession);
 
 		/* Retrieve current JQTI state and set up JQTI controller */
-		final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-		final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+		NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
 		//final ItemSessionController itemSessionController = this.candidateDataService.createItemSessionController(mostRecentEvent, notificationRecorder);
-		final ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
+		ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
 
 		/* Make sure an attempt is allowed */
 		if (itemSessionState.isEnded()) {
@@ -410,13 +402,13 @@ public class AssessmentItemDisplayController extends BasicController implements
 		lastEvent = candidateEvent;
 
 		/* Record current result state, or finish session */
-		updateSessionFinishedStatus(ureq, candidateSession, itemSessionController);
+		updateSessionFinishedStatus(ureq);
 	}
 	
-    private UserTestSession updateSessionFinishedStatus(UserRequest ureq, UserTestSession candidateSession, ItemSessionController itemSessionController) {
+    private UserTestSession updateSessionFinishedStatus(UserRequest ureq) {
         /* Record current result state and maybe close session */
         final ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
-        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq, candidateSession, itemSessionController);
+        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
         if (itemSessionState.isEnded()) {
             qtiService.finishItemSession(candidateSession, assessmentResult, null);
         }
@@ -430,13 +422,13 @@ public class AssessmentItemDisplayController extends BasicController implements
         return candidateSession;
     }
     
-    public AssessmentResult computeAndRecordItemAssessmentResult(UserRequest ureq, UserTestSession candidateSession, ItemSessionController itemSessionController) {
-        final AssessmentResult assessmentResult = computeItemAssessmentResult(ureq, candidateSession, itemSessionController);
+    public AssessmentResult computeAndRecordItemAssessmentResult(UserRequest ureq) {
+        final AssessmentResult assessmentResult = computeItemAssessmentResult(ureq);
         qtiService.recordItemAssessmentResult(candidateSession, assessmentResult);
         return assessmentResult;
     }
     
-    public AssessmentResult computeItemAssessmentResult(UserRequest ureq, UserTestSession candidateSession, ItemSessionController itemSessionController) {
+    public AssessmentResult computeItemAssessmentResult(UserRequest ureq) {
     	String baseUrl = "http://localhost:8080/olat";
         final URI sessionIdentifierSourceId = URI.create(baseUrl);
         final String sessionIdentifier = "itemsession/" + (candidateSession == null ? "sdfj" : candidateSession.getKey());
@@ -449,11 +441,8 @@ public class AssessmentItemDisplayController extends BasicController implements
         //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
         //assertSessionNotTerminated(candidateSession);
 
-        /* Retrieve current JQTI state and set up JQTI controller */
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        //final ItemSessionController itemSessionController = candidateDataService.createItemSessionController(mostRecentEvent, notificationRecorder);
-        final ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
+        NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
 
         /* Make sure caller may do this */
         boolean allowSolutionWhenOpen = true;//itemDeliverySettings.isAllowSolutionWhenOpen()
@@ -486,7 +475,7 @@ public class AssessmentItemDisplayController extends BasicController implements
         }
 
         /* Record current result state, and maybe close session */
-        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq, candidateSession, itemSessionController);
+        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
         if (isClosingSession) {
             qtiService.finishItemSession(candidateSession, assessmentResult, timestamp);
         }
@@ -505,11 +494,9 @@ public class AssessmentItemDisplayController extends BasicController implements
 		//final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
 		//assertSessionNotTerminated(candidateSession);
 
-        /* Retrieve current JQTI state and set up JQTI controller */
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
         //final ItemSessionController itemSessionController = candidateDataService.createItemSessionController(mostRecentEvent, notificationRecorder);
-        final ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
+        ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
 
         /* Check this is allowed in current state */
         
@@ -538,7 +525,7 @@ public class AssessmentItemDisplayController extends BasicController implements
         }
 
         /* Record current result state */
-        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq, candidateSession, itemSessionController);
+        final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
 
         /* Record and log event */
         final CandidateEvent candidateEvent = qtiService.recordCandidateItemEvent(candidateSession,
@@ -558,11 +545,8 @@ public class AssessmentItemDisplayController extends BasicController implements
 	        //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
 	        //assertSessionNotTerminated(candidateSession);
 
-	        /* Retrieve current JQTI state and set up JQTI controller */
-	        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-	        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-	        //final ItemSessionController itemSessionController = candidateDataService.createItemSessionController(mostRecentEvent, notificationRecorder);
-	        final ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
+	        NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+	        ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
 
 	        /* Are we terminating a session that hasn't already been ended? If so end the session and record final result. */
 	        final Date currentTimestamp = ureq.getRequestTimestamp();
@@ -573,7 +557,7 @@ public class AssessmentItemDisplayController extends BasicController implements
 	            	logError("", e);
 	                return;// handleExplosion(e, candidateSession);
 	            }
-	            final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq, candidateSession, itemSessionController);
+	            final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
 	            qtiService.finishItemSession(candidateSession, assessmentResult, currentTimestamp);
 	        }
 
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
index 98b0f67d8d9..fb9d0a0e1bb 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
@@ -23,6 +23,7 @@ import java.io.File;
 import java.net.URI;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -41,6 +42,10 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.gui.control.generic.modal.DialogBoxController;
 import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.id.context.BusinessControlFactory;
+import org.olat.core.id.context.ContextEntry;
+import org.olat.core.util.resource.OresHelper;
 import org.olat.fileresource.FileResourceManager;
 import org.olat.fileresource.types.ImsQTI21Resource;
 import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator;
@@ -57,7 +62,6 @@ import org.olat.modules.assessment.AssessmentService;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
 
-import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
 import uk.ac.ed.ph.jqtiplus.JqtiPlus;
 import uk.ac.ed.ph.jqtiplus.exception.QtiCandidateStateException;
 import uk.ac.ed.ph.jqtiplus.node.result.AbstractResult;
@@ -117,8 +121,6 @@ public class AssessmentTestDisplayController extends BasicController implements
 	private QTI21Service qtiService;
 	@Autowired
 	private AssessmentService assessmentService;
-	@Autowired
-	private JqtiExtensionManager jqtiExtensionManager;
 	
 	/**
 	 * 
@@ -160,7 +162,7 @@ public class AssessmentTestDisplayController extends BasicController implements
 		}
 
 		/* Handle immediate end of test session */
-        if (testSessionController.getTestSessionState().isEnded()) {
+        if (testSessionController.getTestSessionState() != null && testSessionController.getTestSessionState().isEnded()) {
         	AssessmentResult assessmentResult = null;
             qtiService.finishTestSession(candidateSession, assessmentResult, ureq.getRequestTimestamp());
         	mainVC = createVelocityContainer("end");
@@ -203,10 +205,6 @@ public class AssessmentTestDisplayController extends BasicController implements
 		return currentRequestTimestamp;
 	}
 
-	protected CandidateEvent assertSessionEntered(UserTestSession candidateSession) {
-		return lastEvent;
-	}
-
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
 		//
@@ -245,7 +243,7 @@ public class AssessmentTestDisplayController extends BasicController implements
 				processReviewItem(ureq, qe.getSubCommand());
 				break;
 			case itemSolution:
-				processItemSolution(qe.getSubCommand());
+				processItemSolution(ureq, qe.getSubCommand());
 				break;
 			case testPartNavigation:
 				processTestPartNavigation(ureq);
@@ -291,15 +289,10 @@ public class AssessmentTestDisplayController extends BasicController implements
 	
 	private void processReviewItem(UserRequest ureq, String key) {
 		TestPlanNodeKey itemKey = TestPlanNodeKey.fromString(key);
-		Date requestTimestamp = ureq.getRequestTimestamp();
-		
         //Assert.notNull(itemKey, "itemKey");
 
-        /* Get current JQTI state and create JQTI controller */
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder);
-        final TestSessionState testSessionState = testSessionController.getTestSessionState();
+		NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        TestSessionState testSessionState = testSessionController.getTestSessionState();
 
         /* Make sure caller may do this */
         //assertSessionNotTerminated(candidateSession);
@@ -319,7 +312,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         }
 
         /* Record current result state */
-        computeAndRecordTestAssessmentResult(candidateSession, testSessionController, false);
+        computeAndRecordTestAssessmentResult(ureq, false);
 
         /* Record and log event */
         final CandidateEvent candidateTestEvent = qtiService.recordCandidateTestEvent(candidateSession,
@@ -328,13 +321,10 @@ public class AssessmentTestDisplayController extends BasicController implements
         //candidateAuditLogger.logCandidateEvent(candidateTestEvent);
 	}
 
-	private void processItemSolution(String key) {
+	private void processItemSolution(UserRequest ureq, String key) {
 		TestPlanNodeKey itemKey = TestPlanNodeKey.fromString(key);
 
-        /* Get current JQTI state and create JQTI controller */
         NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder);
         TestSessionState testSessionState = testSessionController.getTestSessionState();
 
         /* Make sure caller may do this */
@@ -356,7 +346,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         }
 
         /* Record current result state */
-        computeAndRecordTestAssessmentResult(candidateSession, testSessionController, false);
+        computeAndRecordTestAssessmentResult(ureq, false);
 
         /* Record and log event */
         CandidateEvent candidateTestEvent = qtiService.recordCandidateTestEvent(candidateSession,
@@ -368,12 +358,8 @@ public class AssessmentTestDisplayController extends BasicController implements
 	//public CandidateSession finishLinearItem(final CandidateSessionContext candidateSessionContext)
     // throws CandidateException {
 	private void processFinish(UserRequest ureq) {
-		
-        /* Get current JQTI state and create JQTI controller */
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder);
-        final TestSessionState testSessionState = testSessionController.getTestSessionState();
+		NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        TestSessionState testSessionState = testSessionController.getTestSessionState();
 		
 		try {
 			if (!testSessionController.mayAdvanceItemLinear()) {
@@ -392,10 +378,10 @@ public class AssessmentTestDisplayController extends BasicController implements
 		final Date requestTimestamp = ureq.getRequestTimestamp();
 	    final TestPlanNode nextItemNode = testSessionController.advanceItemLinear(requestTimestamp);
 	    
-	    boolean terminated = nextItemNode == null && testSessionController.findNextEnterableTestPart() == null; 
+	    //boolean terminated = nextItemNode == null && testSessionController.findNextEnterableTestPart() == null; 
 
 	    // Record current result state
-	    final AssessmentResult assessmentResult = computeAndRecordTestAssessmentResult(candidateSession, testSessionController, terminated);
+	    final AssessmentResult assessmentResult = computeAndRecordTestAssessmentResult(ureq, false);
 
 	    /* If we ended the testPart and there are now no more available testParts, then finish the session now */
 	    if (nextItemNode==null && testSessionController.findNextEnterableTestPart()==null) {
@@ -427,11 +413,8 @@ public class AssessmentTestDisplayController extends BasicController implements
         //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
         //assertSessionNotTerminated(candidateSession);
 
-        /* Get current JQTI state and create JQTI controller */
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder);
-        final TestSessionState testSessionState = testSessionController.getTestSessionState();
+		NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        TestSessionState testSessionState = testSessionController.getTestSessionState();
 		
 		final Map<Identifier, ResponseData> responseDataMap = new HashMap<Identifier, ResponseData>();
         if (stringResponseMap != null) {
@@ -495,7 +478,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         
         
         /* Record current result state */
-        computeAndRecordTestAssessmentResult(candidateSession, testSessionController, false);
+        computeAndRecordTestAssessmentResult(ureq, false);
 
         /* Save any change to session state */
         candidateSession = qtiService.updateTestSession(candidateSession);
@@ -517,13 +500,9 @@ public class AssessmentTestDisplayController extends BasicController implements
 	}
 	
 	private void processAdvanceTestPart(UserRequest ureq) {
-		
-		//final CandidateSessionContext candidateSessionContext = getCandidateSessionContext();
-		
         /* Get current JQTI state and create JQTI controller */
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        final TestSessionState testSessionState = testSessionController.getTestSessionState();
+        NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        TestSessionState testSessionState = testSessionController.getTestSessionState();
 
         /* Perform action */
         final TestPlanNode nextTestPart;
@@ -563,7 +542,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         boolean terminated = isTerminated();
 
         /* Record current result state */
-        computeAndRecordTestAssessmentResult(candidateSession, testSessionController, terminated);
+        computeAndRecordTestAssessmentResult(ureq, terminated);
 
         /* Record and log event */
         final CandidateEvent candidateTestEvent = qtiService.recordCandidateTestEvent(candidateSession,
@@ -577,17 +556,12 @@ public class AssessmentTestDisplayController extends BasicController implements
 	}
 	
 	private void processReviewTestPart() {
-		
-        /* Get current JQTI state and create JQTI controller */
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder);
-        final TestSessionState testSessionState = testSessionController.getTestSessionState();
+		NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        TestSessionState testSessionState = testSessionController.getTestSessionState();
 
         /* Make sure caller may do this */
         //assertSessionNotTerminated(candidateSession);
         if (testSessionState.getCurrentTestPartKey()==null || !testSessionState.getCurrentTestPartSessionState().isEnded()) {
-        	
             // candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_REVIEW_TEST_PART);
             logError("CANNOT_REVIEW_TEST_PART", null);
         	return;
@@ -604,12 +578,8 @@ public class AssessmentTestDisplayController extends BasicController implements
 	 * Exit multi-part tests
 	 */
 	private void processExitTest(UserRequest ureq) {
-
-        /* Get current JQTI state and create JQTI controller */
-        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
-        final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession);
-        //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder);
-        final TestSessionState testSessionState = testSessionController.getTestSessionState();
+        NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+        TestSessionState testSessionState = testSessionController.getTestSessionState();
 
         /* Perform action */
         final Date currentTimestamp = ureq.getRequestTimestamp();
@@ -629,7 +599,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         candidateSession = qtiService.updateTestSession(candidateSession);
 
         /* Record current result state (final) */
-        computeAndRecordTestAssessmentResult(candidateSession, testSessionController, true);
+        computeAndRecordTestAssessmentResult(ureq, true);
 
         /* Record and log event */
         final CandidateEvent candidateTestEvent = qtiService.recordCandidateTestEvent(candidateSession,
@@ -646,7 +616,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
 
         /* Create fresh JQTI+ state & controller for it */
-        TestSessionController testSessionController = createNewTestSessionStateAndController(notificationRecorder);
+        testSessionController = createNewTestSessionStateAndController(notificationRecorder);
         if (testSessionController == null) {
             return null;
         }
@@ -684,7 +654,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         boolean ended = testSessionState.isEnded();
 
         /* Record current result state */
-        final AssessmentResult assessmentResult = computeAndRecordTestAssessmentResult(candidateSession, testSessionController, ended);
+        final AssessmentResult assessmentResult = computeAndRecordTestAssessmentResult(ureq, ended);
 
         /* Handle immediate end of test session */
         if (ended) {
@@ -709,7 +679,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         testSessionControllerSettings.setTemplateProcessingLimit(computeTemplateProcessingLimit());
 
         /* Create controller and wire up notification recorder */
-        final TestSessionController result = new TestSessionController(jqtiExtensionManager,
+        final TestSessionController result = new TestSessionController(qtiService.jqtiExtensionManager(),
                 testSessionControllerSettings, testProcessingMap, testSessionState);
         if (notificationRecorder!=null) {
             result.addNotificationListener(notificationRecorder);
@@ -739,7 +709,7 @@ public class AssessmentTestDisplayController extends BasicController implements
         testSessionControllerSettings.setTemplateProcessingLimit(computeTemplateProcessingLimit());
 
         /* Create controller and wire up notification recorder (if passed) */
-        final TestSessionController result = new TestSessionController(jqtiExtensionManager,
+        final TestSessionController result = new TestSessionController(qtiService.jqtiExtensionManager(),
                 testSessionControllerSettings, testProcessingMap, testSessionState);
         if (notificationRecorder!=null) {
             result.addNotificationListener(notificationRecorder);
@@ -748,9 +718,8 @@ public class AssessmentTestDisplayController extends BasicController implements
         return result;
     }
 	
-	private AssessmentResult computeAndRecordTestAssessmentResult(UserTestSession candidateSession,
-			TestSessionController testSessionController, boolean submit) {
-		AssessmentResult assessmentResult = computeTestAssessmentResult(candidateSession, testSessionController);
+	private AssessmentResult computeAndRecordTestAssessmentResult(UserRequest ureq, boolean submit) {
+		AssessmentResult assessmentResult = computeTestAssessmentResult(ureq, candidateSession);
 		qtiService.recordTestAssessmentResult(candidateSession, assessmentResult);
 		processOutcomeVariables(assessmentResult.getTestResult(), submit);
 		return assessmentResult;
@@ -778,22 +747,24 @@ public class AssessmentTestDisplayController extends BasicController implements
             }
         }
         
-        if(score != null) {
+        if(score != null || pass != null) {
         	if(submit) {
-        		outcomesListener.updateOutcomes(score, pass);
-        	} else {
         		outcomesListener.submit(score, pass);
+        	} else {
+        		outcomesListener.updateOutcomes(score, pass);
         	}
         }
     }
 	
-    private AssessmentResult computeTestAssessmentResult(final UserTestSession candidateSession, final TestSessionController testSessionController) {
-    	String baseUrl = "http://localhost:8080/olat";
-        final URI sessionIdentifierSourceId = URI.create(baseUrl);
-        final String sessionIdentifier = "testsession/" + candidateSession.getKey();
-        
-        Date timestamp = new Date();//requestTimestampContext.getCurrentRequestTimestamp();
-        return testSessionController.computeAssessmentResult(timestamp, sessionIdentifier, sessionIdentifierSourceId);
+    private AssessmentResult computeTestAssessmentResult(UserRequest ureq, final UserTestSession testSession) {
+    	List<ContextEntry> entries = getWindowControl().getBusinessControl().getEntries();
+    	OLATResourceable testSessionOres = OresHelper.createOLATResourceableInstance("TestSession", testSession.getKey());
+    	entries.add(BusinessControlFactory.getInstance().createContextEntry(testSessionOres));
+    	String url = BusinessControlFactory.getInstance().getAsAuthURIString(entries, true);
+        final URI sessionIdentifierSourceId = URI.create(url);
+        final String sessionIdentifier = "testsession/" + testSession.getKey();
+        return testSessionController
+        		.computeAssessmentResult(ureq.getRequestTimestamp(), sessionIdentifier, sessionIdentifierSourceId);
     }
 	
 	private TestProcessingMap getTestProcessingMap() {
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
index 221a8a01938..4c63f1b1dd1 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
@@ -63,6 +63,7 @@ import javax.xml.transform.stream.StreamResult;
 import org.apache.commons.io.IOUtils;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.context.Context;
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.components.DefaultComponentRenderer;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormUIFactory;
@@ -79,6 +80,7 @@ import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
+import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.ui.rendering.XmlUtilities;
 import org.xml.sax.InputSource;
 import org.xml.sax.XMLReader;
@@ -142,8 +144,6 @@ import uk.ac.ed.ph.jqtiplus.value.BaseType;
 import uk.ac.ed.ph.jqtiplus.value.Cardinality;
 import uk.ac.ed.ph.jqtiplus.value.SingleValue;
 import uk.ac.ed.ph.jqtiplus.value.Value;
-import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ClassPathResourceLocator;
-import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.SimpleXsltStylesheetCache;
 import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetManager;
 import uk.ac.ed.ph.qtiworks.mathassess.MathEntryInteraction;
 
@@ -1023,7 +1023,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent
 			return;
 		}
 
-		XsltStylesheetManager stylesheetManager = new XsltStylesheetManager(new ClassPathResourceLocator(), new SimpleXsltStylesheetCache());
+		XsltStylesheetManager stylesheetManager = CoreSpringFactory.getImpl(QTI21Service.class).getXsltStylesheetManager();
     	final TransformerHandler mathmlTransformerHandler = stylesheetManager.getCompiledStylesheetHandler(ctopXsltUri, null);
 
         try {
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
index be713d6a5b4..505e859d82e 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
@@ -36,6 +36,7 @@ import javax.xml.transform.TransformerFactoryConfigurationError;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.form.flexible.impl.Form;
 import org.olat.core.gui.components.form.flexible.impl.FormJSHelper;
@@ -47,6 +48,7 @@ import org.olat.core.gui.render.URLBuilder;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.logging.OLATRuntimeException;
 import org.olat.core.util.StringHelper;
+import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.UserTestSession;
 import org.olat.ims.qti21.model.CandidateTestEventType;
 import org.olat.ims.qti21.model.jpa.CandidateEvent;
@@ -54,7 +56,6 @@ import org.olat.ims.qti21.ui.CandidateSessionContext;
 import org.olat.ims.qti21.ui.QTIWorksAssessmentTestEvent.Event;
 import org.w3c.dom.Element;
 
-import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
 import uk.ac.ed.ph.jqtiplus.node.ForeignElement;
 import uk.ac.ed.ph.jqtiplus.node.QtiNode;
 import uk.ac.ed.ph.jqtiplus.node.content.basic.TextRun;
@@ -532,7 +533,7 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe
 				|| (!identifierMatch && testFeedback.getVisibilityMode() == VisibilityMode.HIDE_IF_MATCH)) {
 			sb.append("<h2>Feedback</h2>");
 			
-			final QtiSerializer serializer = new QtiSerializer(new JqtiExtensionManager());
+			final QtiSerializer serializer = CoreSpringFactory.getImpl(QTI21Service.class).qtiSerializer();
 			//TODO QTI flow: need to handle url, feedbackBlock... -->
 			testFeedback.getChildren().forEach((flow) -> sb.append(serializer.serializeJqtiObject(flow)));
 		}
@@ -569,7 +570,7 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe
 		}
 		String endTestTitle = multiPartTest ?
 				translator.translate("assessment.test.end.testPart") : translator.translate("assessment.test.end.test");
-		sb.append(";\" class='btn btn-default'").append(" disabled", !allowedToEndTestPart).append("><span>")
+		sb.append(";\" class='btn btn-default o_sel_end_testpart'").append(" disabled", !allowedToEndTestPart).append("><span>")
 		  .append(endTestTitle).append("</span>");
 
 		sb.append("</button>");
-- 
GitLab