/** * <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.assessment; import java.io.File; import java.math.BigDecimal; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.StringHelper; import org.olat.course.assessment.AssessmentHelper; import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator; import org.olat.ims.qti21.AssessmentItemSession; import org.olat.ims.qti21.AssessmentSessionAuditLogger; import org.olat.ims.qti21.AssessmentTestHelper; import org.olat.ims.qti21.AssessmentTestSession; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.ParentPartItemRefs; import org.olat.ims.qti21.model.xml.QtiNodesExtractor; import org.olat.ims.qti21.ui.CandidateSessionContext; import org.olat.ims.qti21.ui.ResourcesMapper; import org.olat.ims.qti21.ui.components.InteractionResultFormItem; import org.olat.modules.assessment.AssessmentEntry; import org.olat.modules.assessment.AssessmentService; import org.olat.repository.RepositoryEntry; import org.olat.user.UserManager; import org.springframework.beans.factory.annotation.Autowired; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.DrawingInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.EndAttemptInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.ExtendedTextInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.UploadInteraction; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; import uk.ac.ed.ph.jqtiplus.state.TestPlan; import uk.ac.ed.ph.jqtiplus.state.TestPlanNode; import uk.ac.ed.ph.jqtiplus.state.TestPlanNode.TestNodeType; import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey; import uk.ac.ed.ph.jqtiplus.state.TestSessionState; import uk.ac.ed.ph.jqtiplus.types.Identifier; import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; /** * * Initial date: 15.08.2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class IdentityAssessmentTestCorrectionController extends FormBasicController { private RepositoryEntry testEntry; private AssessmentEntry assessmentEntry; private AssessmentTestSession candidateSession; private int count = 0; private String mapperUri; private final URI assessmentObjectUri; private TestSessionState testSessionState; private final ResourceLocator inputResourceLocator; private final CandidateSessionContext candidateSessionContext; private final List<IdentityAssessmentItemWrapper> itemResults = new ArrayList<>(); private final ResolvedAssessmentTest resolvedAssessmentTest; private final Map<String,AssessmentItemSession> identifierToItemSession = new HashMap<>(); private TextElement testCommentEl; @Autowired private QTI21Service qtiService; @Autowired private UserManager userManager; @Autowired private AssessmentService assessmentService; public IdentityAssessmentTestCorrectionController(UserRequest ureq, WindowControl wControl, AssessmentTestSession session) { super(ureq, wControl, "user_interactions"); this.candidateSession = session; testEntry = session.getTestEntry(); FileResourceManager frm = FileResourceManager.getInstance(); File fUnzippedDirRoot = frm.unzipFileResource(testEntry.getOlatResource()); ResourceLocator fileResourceLocator = new PathResourceLocator(fUnzippedDirRoot.toPath()); inputResourceLocator = ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator); assessmentObjectUri = qtiService.createAssessmentTestUri(fUnzippedDirRoot); File submissionDir = qtiService.getSubmissionDirectory(candidateSession); mapperUri = registerCacheableMapper(null, "QTI21CorrectionResources::" + session.getKey(), new ResourcesMapper(assessmentObjectUri, submissionDir)); resolvedAssessmentTest = qtiService.loadAndResolveAssessmentTest(fUnzippedDirRoot, false, false); testSessionState = qtiService.loadTestSessionState(candidateSession); candidateSessionContext = new TerminatedStaticCandidateSessionContext(candidateSession); assessmentEntry = assessmentService.loadAssessmentEntry(session.getIdentity(), session.getRepositoryEntry(), session.getSubIdent()); initForm(ureq); } public AssessmentTestSession getAssessmentTestSession() { return candidateSession; } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { List<AssessmentItemSession> itemSessions = qtiService.getAssessmentItemSessions(candidateSession); for(AssessmentItemSession itemSession:itemSessions) { identifierToItemSession.put(itemSession.getAssessmentItemIdentifier(), itemSession); } if(formLayout instanceof FormLayoutContainer) { FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout; layoutCont.contextPut("interactionWrappers", itemResults); Map<Identifier, AssessmentItemRef> identifierToRefs = new HashMap<>(); for(AssessmentItemRef itemRef:resolvedAssessmentTest.getAssessmentItemRefs()) { identifierToRefs.put(itemRef.getIdentifier(), itemRef); } TestPlan testPlan = testSessionState.getTestPlan(); List<TestPlanNode> nodes = testPlan.getTestPlanNodeList(); for(TestPlanNode node:nodes) { TestNodeType testNodeType = node.getTestNodeType(); if(testNodeType == TestNodeType.ASSESSMENT_ITEM_REF) { initFormItemResult(layoutCont, node, identifierToRefs); } } } String comment = ""; if(assessmentEntry != null && StringHelper.containsNonWhitespace(assessmentEntry.getComment())) { comment = assessmentEntry.getComment(); } testCommentEl = uifactory.addTextAreaElement("comment.test", "comment.test", 2500, 4, 72, true, comment, formLayout); FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); formLayout.add(buttonsCont); buttonsCont.setRootForm(mainForm); uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl()); uifactory.addFormSubmitButton("save", buttonsCont); } private void initFormItemResult(FormLayoutContainer layoutCont, TestPlanNode node, Map<Identifier, AssessmentItemRef> identifierToRefs) { TestPlanNodeKey testPlanNodeKey = node.getKey(); Identifier identifier = testPlanNodeKey.getIdentifier(); AssessmentItemRef itemRef = identifierToRefs.get(identifier); ResolvedAssessmentItem resolvedAssessmentItem = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef); AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); boolean manualScore = false; List<Interaction> interactions = assessmentItem.getItemBody().findInteractions(); List<InteractionResultFormItem> responseItems = new ArrayList<>(); for(Interaction interaction:interactions) { if(!(interaction instanceof EndAttemptInteraction)) { responseItems.add(initFormExtendedTextInteraction(resolvedAssessmentItem, testPlanNodeKey, interaction, layoutCont)); } if(interaction instanceof UploadInteraction || interaction instanceof DrawingInteraction || interaction instanceof ExtendedTextInteraction) { manualScore = true; } } if(responseItems.size() > 0) { String mScore = ""; String comment = ""; String stringuifiedIdentifier = identifier.toString(); AssessmentItemSession itemSession = null; if(identifierToItemSession.containsKey(stringuifiedIdentifier)) { itemSession = identifierToItemSession.get(stringuifiedIdentifier); comment = itemSession.getCoachComment(); if(itemSession.getManualScore() != null) { mScore = AssessmentHelper.getRoundedScore(itemSession.getManualScore()); } } ItemSessionState itemSessionState = testSessionState.getItemSessionStates().get(testPlanNodeKey); AssessmentItemCorrection infos = new AssessmentItemCorrection(candidateSession.getIdentity(), candidateSession, testSessionState, itemSession, itemSessionState, itemRef, node); String fullname = userManager.getUserDisplayName(candidateSession.getIdentity()); TextElement scoreEl = null; if(manualScore) { scoreEl = uifactory.addTextElement("score_item_" + count++, "score", 6, mScore, layoutCont); } TextElement commentEl = uifactory.addTextAreaElement("comment_item_" + count++, "comment", 2500, 4, 72, true, comment, layoutCont); IdentityAssessmentItemWrapper wrapper = new IdentityAssessmentItemWrapper(fullname, assessmentItem, infos, responseItems, scoreEl, commentEl); if(manualScore) { Double minScore = QtiNodesExtractor.extractMinScore(assessmentItem); Double maxScore = QtiNodesExtractor.extractMaxScore(assessmentItem); if(maxScore != null) { if(minScore == null) { minScore = 0.0d; } wrapper.setMinScore(AssessmentHelper.getRoundedScore(minScore)); wrapper.setMaxScore(AssessmentHelper.getRoundedScore(maxScore)); wrapper.setMinScoreVal(minScore); wrapper.setMaxScoreVal(maxScore); scoreEl.setExampleKey("correction.min.max.score", new String[]{ wrapper.getMinScore(), wrapper.getMaxScore() }); } } itemResults.add(wrapper); } } private InteractionResultFormItem initFormExtendedTextInteraction(ResolvedAssessmentItem resolvedAssessmentItem, TestPlanNodeKey testPlanNodeKey, Interaction interaction, FormLayoutContainer layoutCont) { ItemSessionState sessionState = testSessionState.getItemSessionStates().get(testPlanNodeKey); String responseId = "responseItem" + count++; InteractionResultFormItem responseFormItem = new InteractionResultFormItem(responseId, interaction, resolvedAssessmentItem); responseFormItem.setItemSessionState(sessionState); responseFormItem.setCandidateSessionContext(candidateSessionContext); responseFormItem.setResolvedAssessmentTest(resolvedAssessmentTest); responseFormItem.setResourceLocator(inputResourceLocator); responseFormItem.setAssessmentObjectUri(assessmentObjectUri); responseFormItem.setMapperUri(mapperUri); layoutCont.add(responseFormItem); return responseFormItem; } @Override protected void doDispose() { // } @Override protected boolean validateFormLogic(UserRequest ureq) { boolean allOk = true; if(itemResults != null) { for(IdentityAssessmentItemWrapper itemResult:itemResults) { if(itemResult.getScoreEl() != null) { allOk &= validateScore(itemResult); } } } return allOk & super.validateFormLogic(ureq); } private boolean validateScore(IdentityAssessmentItemWrapper itemResult) { boolean allOk = true; String scoreVal = itemResult.getScoreEl().getValue(); itemResult.getScoreEl().clearError(); try { double score = Double.parseDouble(scoreVal); //check boundaries boolean boundariesOk = true; if(itemResult.getMinScore() != null && score < itemResult.getMinScoreVal().doubleValue()) { boundariesOk &= false; } if(itemResult.getMaxScore() != null && score > itemResult.getMaxScoreVal().doubleValue()) { boundariesOk &= false; } if(!boundariesOk) { itemResult.getScoreEl() .setErrorKey("correction.min.max.score", new String[]{ itemResult.getMinScore(), itemResult.getMaxScore() }); } allOk &= boundariesOk; } catch(Exception e) { itemResult.getScoreEl().setErrorKey("form.error.nointeger", null); allOk &= false; } return allOk; } @Override protected void formOK(UserRequest ureq) { if(itemResults != null) { AssessmentSessionAuditLogger candidateAuditLogger = qtiService.getAssessmentSessionAuditLogger(candidateSession, false); BigDecimal totalScore = new BigDecimal(0); for(IdentityAssessmentItemWrapper itemResult:itemResults) { totalScore = doUpdateItemSession(itemResult, totalScore, candidateAuditLogger); } candidateSession.setManualScore(totalScore); candidateSession = qtiService.updateAssessmentTestSession(candidateSession); String comment = testCommentEl.getValue(); if(assessmentEntry != null && !comment.equals(assessmentEntry.getComment())) { assessmentEntry = assessmentService.loadAssessmentEntry(candidateSession.getIdentity(), candidateSession.getRepositoryEntry(), candidateSession.getSubIdent()); assessmentEntry.setComment(comment); assessmentEntry = assessmentService.updateAssessmentEntry(assessmentEntry); } } fireEvent(ureq, Event.DONE_EVENT); } private BigDecimal doUpdateItemSession(IdentityAssessmentItemWrapper itemResult, BigDecimal totalScore, AssessmentSessionAuditLogger candidateAuditLogger) { String scoreVal = null; if(itemResult.getScoreEl() != null) { scoreVal = itemResult.getScoreEl().getValue(); } String comment = null; if(itemResult.getCommentEl() != null) { comment = itemResult.getCommentEl().getValue(); } if(StringHelper.containsNonWhitespace(scoreVal) || comment != null) { String stringuifiedIdentifier = itemResult .getTestPlanNodeKey().getIdentifier().toString(); ParentPartItemRefs parentParts = getParentSection(itemResult.getTestPlanNodeKey()); AssessmentItemSession itemSession = qtiService .getOrCreateAssessmentItemSession(candidateSession, parentParts, stringuifiedIdentifier); if(StringHelper.containsNonWhitespace(scoreVal)) { BigDecimal mScore = new BigDecimal(scoreVal); itemSession.setManualScore(mScore); totalScore = totalScore.add(mScore); } if(comment != null) { itemSession.setCoachComment(comment); } itemSession = qtiService.updateAssessmentItemSession(itemSession); candidateAuditLogger.logCorrection(candidateSession, itemSession, getIdentity()); } return totalScore; } private ParentPartItemRefs getParentSection(TestPlanNodeKey itemKey) { return AssessmentTestHelper.getParentSection(itemKey, testSessionState, resolvedAssessmentTest); } @Override protected void formCancelled(UserRequest ureq) { fireEvent(ureq, Event.CANCELLED_EVENT); } }