/**
* <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;
import java.io.File;
import java.net.URI;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.util.FileUtils;
import org.olat.fileresource.types.ImsQTI21Resource;
import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator;
import org.olat.ims.qti21.AssessmentResponse;
import org.olat.ims.qti21.AssessmentSessionAuditLogger;
import org.olat.ims.qti21.AssessmentTestSession;
import org.olat.ims.qti21.QTI21DeliveryOptions;
import org.olat.ims.qti21.QTI21Service;
import org.olat.ims.qti21.model.audit.AssessmentResponseData;
import org.olat.ims.qti21.model.audit.CandidateEvent;
import org.olat.ims.qti21.model.audit.CandidateExceptionReason;
import org.olat.ims.qti21.model.audit.CandidateItemEventType;
import org.olat.ims.qti21.ui.ResponseInput.Base64Input;
import org.olat.ims.qti21.ui.ResponseInput.FileInput;
import org.olat.ims.qti21.ui.ResponseInput.StringInput;
import org.olat.ims.qti21.ui.components.AssessmentItemFormItem;
import org.olat.modules.assessment.AssessmentEntry;
import org.olat.repository.RepositoryEntry;
import org.springframework.beans.factory.annotation.Autowired;
import uk.ac.ed.ph.jqtiplus.JqtiPlus;
import uk.ac.ed.ph.jqtiplus.exception.QtiCandidateStateException;
import uk.ac.ed.ph.jqtiplus.node.result.AssessmentResult;
import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef;
import uk.ac.ed.ph.jqtiplus.notification.NotificationLevel;
import uk.ac.ed.ph.jqtiplus.notification.NotificationRecorder;
import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem;
import uk.ac.ed.ph.jqtiplus.running.ItemProcessingInitializer;
import uk.ac.ed.ph.jqtiplus.running.ItemSessionController;
import uk.ac.ed.ph.jqtiplus.running.ItemSessionControllerSettings;
import uk.ac.ed.ph.jqtiplus.state.ItemProcessingMap;
import uk.ac.ed.ph.jqtiplus.state.ItemSessionState;
import uk.ac.ed.ph.jqtiplus.types.FileResponseData;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.types.ResponseData;
import uk.ac.ed.ph.jqtiplus.types.StringResponseData;
import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator;
/**
*
* Initial date: 22.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class AssessmentItemDisplayController extends BasicController implements CandidateSessionContext {
private final VelocityContainer mainVC;
private QtiWorksController qtiWorksCtrl;
private ItemSessionController itemSessionController;
private final String mapperUri;
private final File fUnzippedDirRoot;
private final File itemFileRef;
private final QTI21DeliveryOptions deliveryOptions;
private final ResolvedAssessmentItem resolvedAssessmentItem;
/* This directory will be deleted at the disposal of the controller */
private File submissionDirToDispose;
private CandidateEvent lastEvent;
private Date currentRequestTimestamp;
private RepositoryEntry entry;
private AssessmentTestSession candidateSession;
private final AssessmentSessionAuditLogger candidateAuditLogger;
@Autowired
private QTI21Service qtiService;
/**
* OPen in memory session
* @param ureq
* @param wControl
* @param authorMode
* @param resolvedAssessmentItem
* @param fUnzippedDirRoot
* @param itemFileRef
*/
public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl, ResolvedAssessmentItem resolvedAssessmentItem,
File fUnzippedDirRoot, File itemFileRef, AssessmentSessionAuditLogger candidateAuditLogger) {
super(ureq, wControl);
this.itemFileRef = itemFileRef;
this.fUnzippedDirRoot = fUnzippedDirRoot;
this.resolvedAssessmentItem = resolvedAssessmentItem;
this.candidateAuditLogger = candidateAuditLogger;
deliveryOptions = QTI21DeliveryOptions.defaultSettings();
currentRequestTimestamp = ureq.getRequestTimestamp();
candidateSession = qtiService.createInMemoryAssessmentTestSession(getIdentity());
submissionDirToDispose = qtiService.getSubmissionDirectory(candidateSession);
mapperUri = registerCacheableMapper(ureq, UUID.randomUUID().toString(), new ResourcesMapper(itemFileRef.toURI(), submissionDirToDispose));
itemSessionController = enterSession(ureq);
if(itemSessionController == null) {
mainVC = createVelocityContainer("error");
} else if (itemSessionController.getItemSessionState().isEnded()) {
mainVC = createVelocityContainer("end");
} else {
mainVC = createVelocityContainer("run");
initQtiWorks(ureq);
}
putInitialPanel(mainVC);
}
public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl,
ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef, File fUnzippedDirRoot,
AssessmentSessionAuditLogger candidateAuditLogger) {
super(ureq, wControl);
this.itemFileRef = new File(fUnzippedDirRoot, itemRef.getHref().toString());
this.fUnzippedDirRoot = fUnzippedDirRoot;
this.resolvedAssessmentItem = resolvedAssessmentItem;
this.candidateAuditLogger = candidateAuditLogger;
deliveryOptions = QTI21DeliveryOptions.defaultSettings();
currentRequestTimestamp = ureq.getRequestTimestamp();
candidateSession = qtiService.createInMemoryAssessmentTestSession(getIdentity());
submissionDirToDispose = qtiService.getSubmissionDirectory(candidateSession);
mapperUri = registerCacheableMapper(ureq, UUID.randomUUID().toString(), new ResourcesMapper(itemFileRef.toURI(), submissionDirToDispose));
itemSessionController = enterSession(ureq);
if(itemSessionController == null) {
mainVC = createVelocityContainer("error");
} else if (itemSessionController.getItemSessionState().isEnded()) {
mainVC = createVelocityContainer("end");
} else {
mainVC = createVelocityContainer("run");
initQtiWorks(ureq);
}
putInitialPanel(mainVC);
}
public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl,
RepositoryEntry testEntry, AssessmentEntry assessmentEntry, boolean authorMode,
ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef,
File fUnzippedDirRoot, File itemFile,
AssessmentSessionAuditLogger candidateAuditLogger) {
super(ureq, wControl);
this.itemFileRef = itemFile;
this.fUnzippedDirRoot = fUnzippedDirRoot;
this.resolvedAssessmentItem = resolvedAssessmentItem;
this.candidateAuditLogger = candidateAuditLogger;
deliveryOptions = QTI21DeliveryOptions.defaultSettings();
currentRequestTimestamp = ureq.getRequestTimestamp();
candidateSession = qtiService.createAssessmentTestSession(getIdentity(), null, assessmentEntry, testEntry, itemRef.getIdentifier().toString(), testEntry, authorMode);
File submissionDir = qtiService.getSubmissionDirectory(candidateSession);
mapperUri = registerCacheableMapper(ureq, UUID.randomUUID().toString(), new ResourcesMapper(itemFileRef.toURI(), submissionDir));
itemSessionController = enterSession(ureq);
if (itemSessionController.getItemSessionState().isEnded()) {
mainVC = createVelocityContainer("end");
} else {
mainVC = createVelocityContainer("run");
initQtiWorks(ureq);
}
putInitialPanel(mainVC);
}
private void initQtiWorks(UserRequest ureq) {
String filename = itemFileRef.getName();
qtiWorksCtrl = new QtiWorksController(ureq, getWindowControl(), filename);
listenTo(qtiWorksCtrl);
mainVC.put("qtirun", qtiWorksCtrl.getInitialComponent());
}
@Override
protected void doDispose() {
if(submissionDirToDispose != null) {
FileUtils.deleteDirsAndFiles(submissionDirToDispose, true, true);
}
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public AssessmentTestSession getCandidateSession() {
return candidateSession;
}
@Override
public CandidateEvent getLastEvent() {
return lastEvent;
}
@Override
public Date getCurrentRequestTimestamp() {
return currentRequestTimestamp;
}
@Override
public boolean isMarked(String itemKey) {
return false;
}
@Override
protected void event(UserRequest ureq, Component source, Event event) {
currentRequestTimestamp = ureq.getRequestTimestamp();
//
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if(qtiWorksCtrl == source) {
if(event instanceof QTIWorksAssessmentItemEvent) {
processQTIEvent(ureq, (QTIWorksAssessmentItemEvent)event);
}
}
super.event(ureq, source, event);
}
private void processQTIEvent(UserRequest ureq, QTIWorksAssessmentItemEvent qe) {
currentRequestTimestamp = ureq.getRequestTimestamp();
switch(qe.getEvent()) {
case solution:
requestSolution(ureq);
break;
case response:
handleResponses(ureq, qe.getStringResponseMap(), qe.getFileResponseMap(), qe.getComment());
break;
case tmpResponse:
handleTemporaryResponses(ureq, qe.getStringResponseMap());
break;
case close:
endSession(ureq);
break;
case exit:
exitSession(ureq);
break;
case resetsoft:
break;
case resethard:
break;
case source:
logError("QtiWorks event source not implemented", null);
break;
case state:
logError("QtiWorks event state not implemented", null);
break;
case validation:
logError("QtiWorks event validation not implemented", null);
break;
case authorview:
logError("QtiWorks event authorview not implemented", null);
break;
case result:
logError("QtiWorks event result not implemented", null);
break;
}
}
private ItemSessionController enterSession(UserRequest ureq /*, final UserTestSession candidateSession */) {
/* Set up listener to record any notifications */
final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
/* Create fresh JQTI+ state Object and try to create controller */
itemSessionController = createNewItemSessionStateAndController(notificationRecorder);
if (itemSessionController==null) {
logError("", null);
return null;//handleExplosion(null, candidateSession);
}
/* Try to Initialise JQTI+ state */
final ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
try {
final Date timestamp = ureq.getRequestTimestamp();
itemSessionController.initialize(timestamp);
itemSessionController.performTemplateProcessing(timestamp);
itemSessionController.enterItem(timestamp);
}
catch (final RuntimeException e) {
logError("", e);
return null;//handleExplosion(null, candidateSession);
}
/* Record and log entry event */
final CandidateEvent candidateEvent = qtiService.recordCandidateItemEvent(candidateSession, null, entry,
CandidateItemEventType.ENTER, itemSessionState, notificationRecorder);
candidateAuditLogger.logCandidateEvent(candidateEvent);
lastEvent = candidateEvent;
/* Record current result state */
final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
/* Handle immediate end of session */
if (itemSessionState.isEnded()) {
qtiService.finishItemSession(candidateSession, assessmentResult, ureq.getRequestTimestamp());
}
return itemSessionController;
}
public ItemSessionController createNewItemSessionStateAndController(NotificationRecorder notificationRecorder) {
/* Resolve the underlying JQTI+ object */
final ItemProcessingMap itemProcessingMap = getItemProcessingMap();
if (itemProcessingMap == null) {
return null;
}
/* Create fresh state for session */
final ItemSessionState itemSessionState = new ItemSessionState();
/* Create config for ItemSessionController */
final ItemSessionControllerSettings itemSessionControllerSettings = new ItemSessionControllerSettings();
itemSessionControllerSettings.setTemplateProcessingLimit(computeTemplateProcessingLimit());
itemSessionControllerSettings.setMaxAttempts(10 /*itemDeliverySettings.getMaxAttempts() */);
/* Create controller and wire up notification recorder */
final ItemSessionController result = new ItemSessionController(qtiService.jqtiExtensionManager(),
itemSessionControllerSettings, itemProcessingMap, itemSessionState);
if (notificationRecorder != null) {
result.addNotificationListener(notificationRecorder);
}
return result;
}
public ItemProcessingMap getItemProcessingMap() {
ItemProcessingMap result = new ItemProcessingInitializer(resolvedAssessmentItem, true).initialize();
return result;
}
public int computeTemplateProcessingLimit() {
final Integer requestedLimit = deliveryOptions.getTemplateProcessingLimit();
if (requestedLimit == null) {
/* Not specified, so use default */
return JqtiPlus.DEFAULT_TEMPLATE_PROCESSING_LIMIT;
}
final int requestedLimitIntValue = requestedLimit.intValue();
return requestedLimitIntValue > 0 ? requestedLimitIntValue : JqtiPlus.DEFAULT_TEMPLATE_PROCESSING_LIMIT;
}
public void handleTemporaryResponses(UserRequest ureq, Map<Identifier, ResponseInput> stringResponseMap) {
/* Retrieve current JQTI state and set up JQTI controller */
NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
/* Make sure an attempt is allowed */
if (itemSessionState.isEnded()) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.RESPONSES_NOT_EXPECTED, null);
logError("RESPONSES_NOT_EXPECTED", null);
return;
}
/* Build response map in required format for JQTI+.
* NB: The following doesn't test for duplicate keys in the two maps. I'm not sure
* it's worth the effort.
*/
final Map<Identifier, ResponseData> responseDataMap = new HashMap<>();
final Map<Identifier, AssessmentResponse> assessmentResponseDataMap = new HashMap<>();
if (stringResponseMap!=null) {
for (final Entry<Identifier, ResponseInput> stringResponseEntry : stringResponseMap.entrySet()) {
Identifier identifier = stringResponseEntry.getKey();
ResponseInput responseData = stringResponseEntry.getValue();
if(responseData instanceof StringInput) {
responseDataMap.put(identifier, new StringResponseData(((StringInput)responseData).getResponseData()));
}
}
}
final Date timestamp = ureq.getRequestTimestamp();
/* Attempt to bind responses */
boolean allResponsesValid = false, allResponsesBound = false;
try {
itemSessionController.bindResponses(timestamp, responseDataMap);
} catch (final QtiCandidateStateException e) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.RESPONSES_NOT_EXPECTED, null);
logError("RESPONSES_NOT_EXPECTED", e);
return;
} catch (final RuntimeException e) {
logError("", e);
return;// handleExplosion(e, candidateSession);
}
/* Record resulting attempt and event */
final CandidateItemEventType eventType = allResponsesBound ?
(allResponsesValid ? CandidateItemEventType.ATTEMPT_VALID : CandidateItemEventType.RESPONSE_INVALID)
: CandidateItemEventType.RESPONSE_BAD;
final CandidateEvent candidateEvent = qtiService.recordCandidateItemEvent(candidateSession, null, entry,
eventType, itemSessionState, notificationRecorder);
candidateAuditLogger.logCandidateEvent(candidateEvent, assessmentResponseDataMap);
lastEvent = candidateEvent;
}
public void handleResponses(UserRequest ureq, Map<Identifier, ResponseInput> stringResponseMap,
Map<Identifier,ResponseInput> fileResponseMap, String candidateComment) {
/* Retrieve current JQTI state and set up JQTI controller */
NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
/* Make sure an attempt is allowed */
if (itemSessionState.isEnded()) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.RESPONSES_NOT_EXPECTED, null);
logError("RESPONSES_NOT_EXPECTED", null);
return;
}
/* Build response map in required format for JQTI+.
* NB: The following doesn't test for duplicate keys in the two maps. I'm not sure
* it's worth the effort.
*/
final Map<Identifier, ResponseData> responseDataMap = new HashMap<>();
//final Map<Identifier, CandidateFileSubmission> fileSubmissionMap = new HashMap<>();
final Map<Identifier, AssessmentResponse> assessmentResponseDataMap = new HashMap<>();
if (stringResponseMap!=null) {
for (final Entry<Identifier, ResponseInput> stringResponseEntry : stringResponseMap.entrySet()) {
Identifier identifier = stringResponseEntry.getKey();
ResponseInput responseData = stringResponseEntry.getValue();
if(responseData instanceof StringInput) {
responseDataMap.put(identifier, new StringResponseData(((StringInput)responseData).getResponseData()));
} else if(responseData instanceof Base64Input) {
//TODO
} else if(responseData instanceof FileInput) {
}
}
}
if (fileResponseMap!=null) {
for (final Entry<Identifier, ResponseInput> fileResponseEntry : fileResponseMap.entrySet()) {
final Identifier identifier = fileResponseEntry.getKey();
final FileInput multipartFile = (FileInput)fileResponseEntry.getValue();
if (!multipartFile.isEmpty()) {
//final CandidateFileSubmission fileSubmission = candidateUploadService.importFileSubmission(candidateSession, multipartFile);
File storedFile = qtiService.importFileSubmission(candidateSession, multipartFile.getMultipartFileInfos());
final FileResponseData fileResponseData = new FileResponseData(storedFile, multipartFile.getContentType(), multipartFile.getFileName());
responseDataMap.put(identifier, fileResponseData);
assessmentResponseDataMap.put(identifier, new AssessmentResponseData(identifier, fileResponseData));
//fileSubmissionMap.put(identifier, fileSubmission);
}
}
}
/* Submit comment (if provided)
* NB: Do this first in case next actions end the item session.
*/
final Date timestamp = ureq.getRequestTimestamp();
if (candidateComment != null) {
try {
itemSessionController.setCandidateComment(timestamp, candidateComment);
} catch (final QtiCandidateStateException e) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANDIDATE_COMMENT_FORBIDDEN, e);
logError("CANDIDATE_COMMENT_FORBIDDEN", null);
return;
} catch (final RuntimeException e) {
logError("", e);
return; //handleExplosion(e, candidateSession);
}
}
/* Attempt to bind responses */
boolean allResponsesValid = false, allResponsesBound = false;
try {
itemSessionController.bindResponses(timestamp, responseDataMap);
/* Note any responses that failed to bind */
final Set<Identifier> badResponseIdentifiers = itemSessionState.getUnboundResponseIdentifiers();
allResponsesBound = badResponseIdentifiers.isEmpty();
/* Now validate the responses according to any constraints specified by the interactions */
if (allResponsesBound) {
final Set<Identifier> invalidResponseIdentifiers = itemSessionState.getInvalidResponseIdentifiers();
allResponsesValid = invalidResponseIdentifiers.isEmpty();
}
/* (We commit responses immediately here) */
itemSessionController.commitResponses(timestamp);
/* Invoke response processing (only if responses are valid) */
if (allResponsesValid) {
itemSessionController.performResponseProcessing(timestamp);
}
} catch (final QtiCandidateStateException e) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.RESPONSES_NOT_EXPECTED, null);
logError("RESPONSES_NOT_EXPECTED", e);
return;
} catch (final RuntimeException e) {
logError("", e);
return;// handleExplosion(e, candidateSession);
}
/* Record resulting attempt and event */
final CandidateItemEventType eventType = allResponsesBound ?
(allResponsesValid ? CandidateItemEventType.ATTEMPT_VALID : CandidateItemEventType.RESPONSE_INVALID)
: CandidateItemEventType.RESPONSE_BAD;
final CandidateEvent candidateEvent = qtiService.recordCandidateItemEvent(candidateSession, null, entry,
eventType, itemSessionState, notificationRecorder);
candidateAuditLogger.logCandidateEvent(candidateEvent, assessmentResponseDataMap);
lastEvent = candidateEvent;
/* Record current result state, or finish session */
updateSessionFinishedStatus(ureq);
}
private AssessmentTestSession updateSessionFinishedStatus(UserRequest ureq) {
/* Record current result state and maybe close session */
final ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
if (itemSessionState.isEnded()) {
qtiService.finishItemSession(candidateSession, assessmentResult, null);
}
else {
if (candidateSession != null && candidateSession.getFinishTime() != null) {
/* (Session is being reopened) */
candidateSession.setFinishTime(null);
candidateSession = qtiService.updateAssessmentTestSession(candidateSession);
}
}
return candidateSession;
}
public AssessmentResult computeAndRecordItemAssessmentResult(UserRequest ureq) {
final AssessmentResult assessmentResult = computeItemAssessmentResult(ureq);
qtiService.recordItemAssessmentResult(candidateSession, assessmentResult, candidateAuditLogger);
return assessmentResult;
}
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());
return itemSessionController.computeAssessmentResult(ureq.getRequestTimestamp(), sessionIdentifier, sessionIdentifierSourceId);
}
public void requestSolution(UserRequest ureq) {
ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
/* Make sure caller may do this */
boolean allowSolutionWhenOpen = true;//itemDeliverySettings.isAllowSolutionWhenOpen()
if (!itemSessionState.isEnded() && !allowSolutionWhenOpen) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.SOLUTION_WHEN_INTERACTING_FORBIDDEN, null);
logError("SOLUTION_WHEN_INTERACTING_FORBIDDEN", null);
return;
} else if (itemSessionState.isEnded() /* && !itemDeliverySettings.isAllowSoftResetWhenEnded() */) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.SOLUTION_WHEN_ENDED_FORBIDDEN, null);
logError("SOLUTION_WHEN_ENDED_FORBIDDEN", null);
return;
}
/* End session if still open */
final Date timestamp = ureq.getRequestTimestamp();
boolean isClosingSession = false;
if (!itemSessionState.isEnded()) {
isClosingSession = true;
try {
itemSessionController.endItem(timestamp);
} catch (final QtiCandidateStateException e) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.SOLUTION_WHEN_ENDED_FORBIDDEN, null);
logError("SOLUTION_WHEN_ENDED_FORBIDDEN", e);
return;
} catch (final RuntimeException e) {
logError("", e);
return;// handleExplosion(e, candidateSession);
}
}
/* Record current result state, and maybe close session */
final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
if (isClosingSession) {
qtiService.finishItemSession(candidateSession, assessmentResult, timestamp);
}
/* Record and log event */
final CandidateEvent candidateEvent = qtiService.recordCandidateItemEvent(candidateSession, null, entry,
CandidateItemEventType.SOLUTION, itemSessionState);
candidateAuditLogger.logCandidateEvent(candidateEvent);
lastEvent = candidateEvent;
}
public void endSession(UserRequest ureq) {
NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
//final ItemSessionController itemSessionController = candidateDataService.createItemSessionController(mostRecentEvent, notificationRecorder);
ItemSessionState itemSessionState = itemSessionController.getItemSessionState();
/* Check this is allowed in current state */
if (itemSessionState.isEnded()) {
logError("END_SESSION_WHEN_ALREADY_ENDED", null);
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.END_SESSION_WHEN_ALREADY_ENDED, null);
return;
} /* else if (!itemDeliverySettings.isAllowEnd()) {
candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.END_SESSION_WHEN_INTERACTING_FORBIDDEN);
return null;
}*/
/* Update state */
final Date timestamp = ureq.getRequestTimestamp();
try {
itemSessionController.endItem(timestamp);
} catch (QtiCandidateStateException e) {
String msg = itemSessionState.isEnded() ? "END_SESSION_WHEN_ALREADY_ENDED" : "END_SESSION_WHEN_INTERACTING_FORBIDDEN";
logError(msg, e);
candidateAuditLogger.logAndThrowCandidateException(candidateSession, itemSessionState.isEnded() ? CandidateExceptionReason.END_SESSION_WHEN_ALREADY_ENDED : CandidateExceptionReason.END_SESSION_WHEN_INTERACTING_FORBIDDEN, null);
return;
}
catch (final RuntimeException e) {
logError("", e);
return; //handleExplosion(e, candidateSession);
}
/* Record current result state */
final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
/* Record and log event */
final CandidateEvent candidateEvent = qtiService.recordCandidateItemEvent(candidateSession, null, entry,
CandidateItemEventType.END, itemSessionState, notificationRecorder);
candidateAuditLogger.logCandidateEvent(candidateEvent);
lastEvent = candidateEvent;
/* Close session */
qtiService.finishItemSession(candidateSession, assessmentResult, timestamp);
}
public void exitSession(UserRequest ureq) {
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();
if (!itemSessionState.isEnded()) {
try {
itemSessionController.endItem(currentTimestamp);
} catch (final RuntimeException e) {
logError("", e);
return;// handleExplosion(e, candidateSession);
}
final AssessmentResult assessmentResult = computeAndRecordItemAssessmentResult(ureq);
qtiService.finishItemSession(candidateSession, assessmentResult, currentTimestamp);
}
/* Update session entity */
candidateSession.setTerminationTime(currentTimestamp);
candidateSession = qtiService.updateAssessmentTestSession(candidateSession);
/* Record and log event */
final CandidateEvent candidateEvent = qtiService.recordCandidateItemEvent(candidateSession, null, entry,
CandidateItemEventType.EXIT, itemSessionState);
lastEvent = candidateEvent;
candidateAuditLogger.logCandidateEvent(candidateEvent);
}
/**
* QtiWorks manage the form tag itself.
*
* Initial date: 20.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
private class QtiWorksController extends AbstractQtiWorksController {
private AssessmentItemFormItem qtiEl;
private final String filename;
public QtiWorksController(UserRequest ureq, WindowControl wControl, String filename) {
super(ureq, wControl, "ff_run");
this.filename = filename;
initForm(ureq);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
mainForm.setMultipartEnabled(true);
FormSubmit submit = uifactory.addFormSubmitButton("submit", formLayout);
qtiEl = new AssessmentItemFormItem("qtirun", submit);
formLayout.add("qtirun", qtiEl);
ResourceLocator fileResourceLocator = new PathResourceLocator(fUnzippedDirRoot.toPath());
final ResourceLocator inputResourceLocator =
ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator);
qtiEl.setResourceLocator(inputResourceLocator);
qtiEl.setItemSessionController(itemSessionController);
qtiEl.setResolvedAssessmentItem(resolvedAssessmentItem);
File manifestPath = new File(fUnzippedDirRoot, filename);
qtiEl.setAssessmentObjectUri(manifestPath.toURI());
qtiEl.setCandidateSessionContext(AssessmentItemDisplayController.this);
qtiEl.setMapperUri(mapperUri);
}
@Override
protected Identifier getResponseIdentifierFromUniqueId(String uniqueId) {
return qtiEl.getInteractionOfResponseUniqueIdentifier(uniqueId).getResponseIdentifier();
}
@Override
protected void formOK(UserRequest ureq) {
processResponse(ureq, qtiEl.getSubmitButton());
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(source == qtiEl) {
if(event instanceof QTIWorksAssessmentItemEvent) {
QTIWorksAssessmentItemEvent qwaie = (QTIWorksAssessmentItemEvent)event;
if(qwaie.getEvent() == QTIWorksAssessmentItemEvent.Event.tmpResponse) {
processTemporaryResponse(ureq);
} else {
fireEvent(ureq, event);
}
}
} else if(source instanceof FormLink) {
FormLink formLink = (FormLink)source;
processResponse(ureq, formLink);
}
super.formInnerEvent(ureq, source, event);
}
@Override
protected void fireTemporaryResponse(UserRequest ureq, Map<Identifier, ResponseInput> stringResponseMap) {
fireEvent(ureq, new QTIWorksAssessmentItemEvent(QTIWorksAssessmentItemEvent.Event.tmpResponse, stringResponseMap, null, null, null));
}
@Override
protected void fireResponse(UserRequest ureq, FormItem source,
Map<Identifier, ResponseInput> stringResponseMap, Map<Identifier, ResponseInput> fileResponseMap,
String comment) {
fireEvent(ureq, new QTIWorksAssessmentItemEvent(QTIWorksAssessmentItemEvent.Event.response, stringResponseMap, fileResponseMap, comment, source));
}
}
}