Skip to content
Snippets Groups Projects
Commit 009c5d5c authored by srosse's avatar srosse
Browse files

no-jira: first database table for QTI 2.1

parent 45dd71a0
No related branches found
No related tags found
No related merge requests found
Showing
with 971 additions and 44 deletions
...@@ -1818,6 +1818,16 @@ ...@@ -1818,6 +1818,16 @@
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</exclusion> </exclusion>
<!--
<exclusion>
<groupId>net.sf.saxon</groupId>
<artifactId>saxon9</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.saxon</groupId>
<artifactId>saxon9-dom</artifactId>
</exclusion>
-->
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
...@@ -1829,6 +1839,16 @@ ...@@ -1829,6 +1839,16 @@
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</exclusion> </exclusion>
<!--
<exclusion>
<groupId>net.sf.saxon</groupId>
<artifactId>saxon9</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.saxon</groupId>
<artifactId>saxon9-dom</artifactId>
</exclusion>
-->
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
...@@ -1840,6 +1860,16 @@ ...@@ -1840,6 +1860,16 @@
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</exclusion> </exclusion>
<!--
<exclusion>
<groupId>net.sf.saxon</groupId>
<artifactId>saxon9</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.saxon</groupId>
<artifactId>saxon9-dom</artifactId>
</exclusion>
-->
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
......
...@@ -129,6 +129,7 @@ ...@@ -129,6 +129,7 @@
<class>org.olat.instantMessaging.model.InstantMessageNotificationImpl</class> <class>org.olat.instantMessaging.model.InstantMessageNotificationImpl</class>
<class>org.olat.ims.qti.statistics.model.QTIStatisticResult</class> <class>org.olat.ims.qti.statistics.model.QTIStatisticResult</class>
<class>org.olat.ims.qti.statistics.model.QTIStatisticResultSet</class> <class>org.olat.ims.qti.statistics.model.QTIStatisticResultSet</class>
<class>org.olat.ims.qti21.model.UserTestSessionImpl</class>
<class>org.olat.modules.qpool.model.PoolImpl</class> <class>org.olat.modules.qpool.model.PoolImpl</class>
<class>org.olat.modules.qpool.model.PoolToItem</class> <class>org.olat.modules.qpool.model.PoolToItem</class>
<class>org.olat.modules.qpool.model.PoolItemShortView</class> <class>org.olat.modules.qpool.model.PoolItemShortView</class>
......
package org.olat.ims.qti21;
/**
*
*
* //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 {
}
...@@ -46,6 +46,8 @@ public class QTI21Module extends AbstractSpringModule { ...@@ -46,6 +46,8 @@ public class QTI21Module extends AbstractSpringModule {
@Override @Override
public void init() { public void init() {
RepositoryHandlerFactory.registerHandler(assessmentHandler, 10); RepositoryHandlerFactory.registerHandler(assessmentHandler, 10);
//Saxon is mandatory, JQTI need XSLT 2.0
//XsltFactoryUtilities.SAXON_TRANSFORMER_FACTORY_CLASS_NAME;
} }
@Override @Override
......
/**
* <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;
import org.olat.core.id.Identity;
import org.olat.repository.RepositoryEntry;
/**
*
* Initial date: 12.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public interface QTI21Service {
public UserTestSession createTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, Identity identity);
}
/**
* <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;
import org.olat.core.id.CreateInfo;
import org.olat.core.id.ModifiedInfo;
/**
*
* Initial date: 12.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public interface UserTestSession extends CreateInfo, ModifiedInfo {
}
package org.olat.ims.qti21.manager;
import org.olat.core.commons.persistence.DB;
import org.olat.core.id.Identity;
import org.olat.ims.qti21.QTI21Service;
import org.olat.ims.qti21.UserTestSession;
import org.olat.repository.RepositoryEntry;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 12.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class QTI21ServiceImpl implements QTI21Service {
@Autowired
private DB dbInstance;
@Autowired
private TestSessionDAO testSessionDAO;
@Override
public UserTestSession createTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, Identity identity) {
return testSessionDAO.createTestSession(testEntry, courseEntry, identity);
}
}
package org.olat.ims.qti21.manager;
import java.util.Date;
import org.olat.core.commons.persistence.DB;
import org.olat.core.id.Identity;
import org.olat.ims.qti21.UserTestSession;
import org.olat.ims.qti21.model.UserTestSessionImpl;
import org.olat.repository.RepositoryEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* Initial date: 12.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
@Service
public class TestSessionDAO {
@Autowired
private DB dbInstance;
public UserTestSession createTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, Identity identity) {
UserTestSessionImpl testSession = new UserTestSessionImpl();
Date now = new Date();
testSession.setCreationDate(now);
testSession.setLastModified(now);
testSession.setTestEntry(testEntry);
testSession.setCourseEntry(courseEntry);
testSession.setAuthorMode(false);
testSession.setExploded(false);
testSession.setIdentity(identity);
dbInstance.getCurrentEntityManager().persist(testSession);
return testSession;
}
}
/* Copyright (c) 2012-2013, University of Edinburgh.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* * Neither the name of the University of Edinburgh nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* This software is derived from (and contains code from) QTItools and MathAssessEngine.
* QTItools is (c) 2008, University of Southampton.
* MathAssessEngine is (c) 2010, University of Edinburgh.
*/
package org.olat.ims.qti21.model;
import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode;
import uk.ac.ed.ph.jqtiplus.node.test.TestPart;
/**
* Encapsulates the test-specific events that can arise when delivering
* a test within a {@link CandidateSession}
*
* @author David McKain
*/
public enum CandidateTestEventType {
/* NB: Observe maximum length for mapped column set in CandidateEvent */
//1234567890123456789012345678901234567890
/**
* Test has been entered.
* <p>
* (In test having one {@link TestPart}, we also attempt
* to enter this automatically. Otherwise, the first {@link TestPart} has to be
* entered explicitly by the candidate via {@link #ADVANCE_TEST_PART}.)
*/
ENTER_TEST,
/**
* Presentation of navigation menu for the current {@link TestPart}, when in nonlinear
* mode and while the {@link TestPart} is still interacting.
*/
SELECT_MENU,
/**
* Ends the current {@link TestPart} and moves it into review state
*/
END_TEST_PART,
/**
* Exits the current {@link TestPart} (if currently inside one), then advances to the next
* available {@link TestPart}, or exits the test if there are no more available.
*/
ADVANCE_TEST_PART,
/**
* Exits the test, either after the combined {@link TestPart} and test feedback page
* (for single-part tests), or the standalone test feedback page for multi-part tests.
*/
EXIT_TEST,
/** Selection of a particular item for interaction (in {@link NavigationMode#NONLINEAR}) */
SELECT_ITEM,
/**
* Finish interaction of item (in {@link NavigationMode#LINEAR}), moving to the next
* enterable item in the testPart.
*/
FINISH_ITEM,
/**
* Finish interaction of item (in {@link NavigationMode#LINEAR}), with no further
* enterable items in the testPart, thus causing the current testPart to end.
*/
FINISH_FINAL_ITEM,
/** Item Event within the currently-selected item */
ITEM_EVENT,
/** Return to Test Part review (while already in review state, i.e. after {@link #REVIEW_ITEM} */
REVIEW_TEST_PART,
/** Review of a particular item */
REVIEW_ITEM,
/** Solution of a particular item (in review state) */
SOLUTION_ITEM,
;
}
package org.olat.ims.qti21.model;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.GenericGenerator;
import org.olat.basesecurity.IdentityImpl;
import org.olat.core.id.Identity;
import org.olat.core.id.Persistable;
import org.olat.ims.qti21.UserTestSession;
import org.olat.repository.RepositoryEntry;
/**
* This a custom implementation of CandidateSession
*
*
* Initial date: 12.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
@Entity(name="qtitestsession")
@Table(name="o_qti_test_session")
public class UserTestSessionImpl implements UserTestSession, Persistable {
private static final long serialVersionUID = -6069133323360142500L;
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "hilo")
@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
private Long key;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
private Date creationDate;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
private Date lastModified;
@ManyToOne(targetEntity=RepositoryEntry.class,fetch=FetchType.LAZY,optional=false)
@JoinColumn(name="fk_entry", nullable=false, insertable=true, updatable=false)
private RepositoryEntry testEntry;
@ManyToOne(targetEntity=RepositoryEntry.class,fetch=FetchType.LAZY,optional=true)
@JoinColumn(name="fk_course", nullable=true, insertable=true, updatable=false)
private RepositoryEntry courseEntry;
@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false)
@JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false)
private Identity identity;
/** Is this session running in author mode? (I.e. providing debugging information) */
@Column(name="q_author_mode", nullable=false, insertable=true, updatable=true)
private boolean authorMode;
/**
* Timestamp indicating when the session has been <strong>finished</strong>.
* This is a QTIWorks specific concept with the following meaning:
* <ul>
* <li>
* A test is marked as finished once the candidate gets to the end of the last
* enterable testPart. At this time, the outcome variables are finalised and will
* be sent back to the LTI TC (if appropriate). A test only finishes once.
* </li>
* <li>
* A standalone item is marked as finished once the item session ends. At this time,
* the outcome variables are sent back to the LTI TC (if appropriate). These variables
* are normally final, but it is currently possible for items to reopen. The session can
* finish again in this case.
* </li>
* </ul>
*/
@Temporal(TemporalType.TIMESTAMP)
@Column(name="q_finish_time", nullable=true, insertable=true, updatable=true)
private Date finishTime;
/**
* Timestamp indicating when the session has been terminated. Session termination can
* occur in two ways:
* <ul>
* <li>When the candidate naturally exits the session</li>
* <li>When the instructor explicitly terminates the session</li>
* </ul>
* Once terminated, a session is no longer available to the candidate.
*/
@Temporal(TemporalType.TIMESTAMP)
@Column(name="q_termination_time", nullable=true, insertable=true, updatable=true)
private Date terminationTime;
/**
* Flag to indicate if this session blew up while running, either because
* the assessment was not runnable, or because of a logic error.
*/
@Column(name="q_exploded", nullable=false, insertable=true, updatable=true)
private boolean exploded;
@Override
public Long getKey() {
return key;
}
public void setKey(Long key) {
this.key = key;
}
@Override
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
@Override
public Date getLastModified() {
return lastModified;
}
@Override
public void setLastModified(Date lastModified) {
this.lastModified = lastModified;
}
public RepositoryEntry getTestEntry() {
return testEntry;
}
public void setTestEntry(RepositoryEntry testEntry) {
this.testEntry = testEntry;
}
public RepositoryEntry getCourseEntry() {
return courseEntry;
}
public void setCourseEntry(RepositoryEntry courseEntry) {
this.courseEntry = courseEntry;
}
public Identity getIdentity() {
return identity;
}
public void setIdentity(Identity identity) {
this.identity = identity;
}
public boolean isAuthorMode() {
return authorMode;
}
public void setAuthorMode(boolean authorMode) {
this.authorMode = authorMode;
}
public boolean isExploded() {
return exploded;
}
public void setExploded(boolean exploded) {
this.exploded = exploded;
}
public Date getFinishTime() {
return finishTime;
}
public void setFinishTime(Date finishTime) {
this.finishTime = finishTime;
}
public Date getTerminationTime() {
return terminationTime;
}
public void setTerminationTime(Date terminationTime) {
this.terminationTime = terminationTime;
}
@Override
public int hashCode() {
return key == null ? -86534687 : key.hashCode();
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj instanceof UserTestSessionImpl) {
UserTestSessionImpl session = (UserTestSessionImpl)obj;
return getKey() != null && getKey().equals(session.getKey());
}
return false;
}
@Override
public boolean equalsByPersistableKey(Persistable persistable) {
return equals(persistable);
}
}
...@@ -40,11 +40,14 @@ import org.olat.fileresource.FileResourceManager; ...@@ -40,11 +40,14 @@ import org.olat.fileresource.FileResourceManager;
import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ImsQTI21Resource;
import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator; import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator;
import org.olat.ims.qti21.QTI21ContentPackage; import org.olat.ims.qti21.QTI21ContentPackage;
import org.olat.ims.qti21.UserTestSession;
import org.olat.ims.qti21.RequestTimestampContext; import org.olat.ims.qti21.RequestTimestampContext;
import org.olat.ims.qti21.model.CandidateTestEventType;
import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntry;
import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager; import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
import uk.ac.ed.ph.jqtiplus.JqtiPlus; import uk.ac.ed.ph.jqtiplus.JqtiPlus;
import uk.ac.ed.ph.jqtiplus.exception.QtiCandidateStateException;
import uk.ac.ed.ph.jqtiplus.node.AssessmentObjectType; import uk.ac.ed.ph.jqtiplus.node.AssessmentObjectType;
import uk.ac.ed.ph.jqtiplus.node.result.AssessmentResult; import uk.ac.ed.ph.jqtiplus.node.result.AssessmentResult;
import uk.ac.ed.ph.jqtiplus.notification.NotificationLevel; import uk.ac.ed.ph.jqtiplus.notification.NotificationLevel;
...@@ -61,6 +64,7 @@ import uk.ac.ed.ph.jqtiplus.running.TestProcessingInitializer; ...@@ -61,6 +64,7 @@ import uk.ac.ed.ph.jqtiplus.running.TestProcessingInitializer;
import uk.ac.ed.ph.jqtiplus.running.TestSessionController; import uk.ac.ed.ph.jqtiplus.running.TestSessionController;
import uk.ac.ed.ph.jqtiplus.running.TestSessionControllerSettings; import uk.ac.ed.ph.jqtiplus.running.TestSessionControllerSettings;
import uk.ac.ed.ph.jqtiplus.state.TestPlan; import uk.ac.ed.ph.jqtiplus.state.TestPlan;
import uk.ac.ed.ph.jqtiplus.state.TestPlanNode;
import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey; import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey;
import uk.ac.ed.ph.jqtiplus.state.TestProcessingMap; import uk.ac.ed.ph.jqtiplus.state.TestProcessingMap;
import uk.ac.ed.ph.jqtiplus.state.TestSessionState; import uk.ac.ed.ph.jqtiplus.state.TestSessionState;
...@@ -82,9 +86,12 @@ public class QTI21DisplayController extends FormBasicController { ...@@ -82,9 +86,12 @@ public class QTI21DisplayController extends FormBasicController {
private QTI21FormItem qtiEl; private QTI21FormItem qtiEl;
private TestSessionController testSessionController; private TestSessionController testSessionController;
private RequestTimestampContext requestTimestampContext = new RequestTimestampContext();
private JqtiExtensionManager jqtiExtensionManager = new JqtiExtensionManager(); private JqtiExtensionManager jqtiExtensionManager = new JqtiExtensionManager();
private RequestTimestampContext requestTimestampContext = new RequestTimestampContext();
private CandidateSessionFinisher candidateSessionFinisher = new CandidateSessionFinisher();
private UserTestSession candidateSession;
public QTI21DisplayController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { public QTI21DisplayController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) {
super(ureq, wControl, "run"); super(ureq, wControl, "run");
...@@ -136,23 +143,173 @@ public class QTI21DisplayController extends FormBasicController { ...@@ -136,23 +143,173 @@ public class QTI21DisplayController extends FormBasicController {
if(source == qtiEl) { if(source == qtiEl) {
if(event instanceof QTIWorksEvent) { if(event instanceof QTIWorksEvent) {
QTIWorksEvent qe = (QTIWorksEvent)event; QTIWorksEvent qe = (QTIWorksEvent)event;
switch(qe.getEvent()) { processQTIEvent(qe);
case selectItem: doSelectItem(qe.getSubCommand()); break;
case testPartNavigation: doTestPartNavigation(); break;
case response: doResponse(qe.getStringResponseMap()); break;
case endTestPart: doEndTestPart(); break;
}
} }
} }
super.formInnerEvent(ureq, source, event); super.formInnerEvent(ureq, source, event);
} }
private void processQTIEvent(QTIWorksEvent qe) {
switch(qe.getEvent()) {
case selectItem:
doSelectItem(qe.getSubCommand());
break;
case finishItem:
doFinish();
break;
case reviewItem:
doReviewItem(qe.getSubCommand());
break;
case itemSolution:
doItemSolution(qe.getSubCommand());
break;
case testPartNavigation:
doTestPartNavigation();
break;
case response:
doResponse(qe.getStringResponseMap());
break;
case endTestPart:
doEndTestPart();
break;
case advanceTestPart:
doAdvanceTestPart();
break;
case reviewTestPart:
doReviewTestPart();
break;
case exitTest:
doExitTest();
break;
}
}
private void doSelectItem(String key) { private void doSelectItem(String key) {
TestPlanNodeKey nodeKey = TestPlanNodeKey.fromString(key); TestPlanNodeKey nodeKey = TestPlanNodeKey.fromString(key);
Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp();
testSessionController.selectItemNonlinear(requestTimestamp, nodeKey); testSessionController.selectItemNonlinear(requestTimestamp, nodeKey);
} }
private void doReviewItem(String key) {
TestPlanNodeKey itemKey = TestPlanNodeKey.fromString(key);
Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp();
//Assert.notNull(candidateSessionContext, "candidateSessionContext");
//Assert.notNull(itemKey, "itemKey");
//assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST);
//final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
/* 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();
/* Make sure caller may do this */
//assertSessionNotTerminated(candidateSession);
try {
if (!testSessionController.mayReviewItem(itemKey)) {
logError("CANNOT_REVIEW_TEST_ITEM", null);
//candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_REVIEW_TEST_ITEM);
return;
}
} catch (final QtiCandidateStateException e) {
logError("CANNOT_REVIEW_TEST_ITEM", e);
// candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_REVIEW_TEST_ITEM);
return;
} catch (final RuntimeException e) {
logError("CANNOT_REVIEW_TEST_ITEM", e);
return;// handleExplosion(e, candidateSession);
}
/* Record current result state */
//candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController);
/* Record and log event */
//final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession,
// CandidateTestEventType.REVIEW_ITEM, null, itemKey, testSessionState, notificationRecorder);
//candidateAuditLogger.logCandidateEvent(candidateTestEvent);
}
private void doItemSolution(String key) {
TestPlanNodeKey itemKey = TestPlanNodeKey.fromString(key);
//Assert.notNull(candidateSessionContext, "candidateSessionContext");
//assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST);
//final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
//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();
/* Make sure caller may do this */
//assertSessionNotTerminated(candidateSession);
try {
if (!testSessionController.mayAccessItemSolution(itemKey)) {
//candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_SOLUTION_TEST_ITEM);
logError("CANNOT_SOLUTION_TEST_ITEM", null);
return;
}
}
catch (final QtiCandidateStateException e) {
//candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_SOLUTION_TEST_ITEM);
logError("CANNOT_SOLUTION_TEST_ITEM", e);
return;
} catch (final RuntimeException e) {
logError("Exploded", e);
return;// handleExplosion(e, candidateSession);
}
/* Record current result state */
//candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController);
/* Record and log event */
//final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession,
// CandidateTestEventType.SOLUTION_ITEM, null, itemKey, testSessionState, notificationRecorder);
//candidateAuditLogger.logCandidateEvent(candidateTestEvent);
}
//public CandidateSession finishLinearItem(final CandidateSessionContext candidateSessionContext)
// throws CandidateException {
private void doFinish() {
try {
if (!testSessionController.mayAdvanceItemLinear()) {
logError("CANNOT_FINISH_LINEAR_TEST_ITEM", null);
return;
}
} catch (QtiCandidateStateException e) {
logError("CANNOT_FINISH_LINEAR_TEST_ITEM", e);
return;
} catch (RuntimeException e) {
logError("CANNOT_FINISH_LINEAR_TEST_ITEM", e);
//return handleExplosion(e, candidateSession);
}
// Update state
final Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp();
final TestPlanNode nextItemNode = testSessionController.advanceItemLinear(requestTimestamp);
// Record current result state
//final AssessmentResult assessmentResult = candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController);
/* If we ended the testPart and there are now no more available testParts, then finish the session now */
if (nextItemNode==null && testSessionController.findNextEnterableTestPart()==null) {
//candidateSessionFinisher.finishCandidateSession(candidateSession, assessmentResult);
}
/* Record and log event
final CandidateTestEventType eventType = nextItemNode!=null ? CandidateTestEventType.FINISH_ITEM : CandidateTestEventType.FINISH_FINAL_ITEM;
final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession,
eventType, null, testSessionState, notificationRecorder);
*/
}
private void doTestPartNavigation() { private void doTestPartNavigation() {
final Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); final Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp();
testSessionController.selectItemNonlinear(requestTimestamp, null); testSessionController.selectItemNonlinear(requestTimestamp, null);
...@@ -193,6 +350,145 @@ public class QTI21DisplayController extends FormBasicController { ...@@ -193,6 +350,145 @@ public class QTI21DisplayController extends FormBasicController {
testSessionController.endCurrentTestPart(requestTimestamp); testSessionController.endCurrentTestPart(requestTimestamp);
} }
private void doAdvanceTestPart() {
//final CandidateSessionContext candidateSessionContext = getCandidateSessionContext();
//Assert.notNull(candidateSessionContext, "candidateSessionContext");
//assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST);
//final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
/* 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();
/* Perform action */
final TestPlanNode nextTestPart;
final Date currentTimestamp = requestTimestampContext.getCurrentRequestTimestamp();
try {
nextTestPart = testSessionController.enterNextAvailableTestPart(currentTimestamp);
} catch (final QtiCandidateStateException e) {
logError("CANNOT_ADVANCE_TEST_PART", e);
return;
} catch (final RuntimeException e) {
logError("RuntimeException", e);
return;// handleExplosion(e, candidateSession);
}
CandidateTestEventType eventType;
if (nextTestPart!=null) {
/* Moved into next test part */
eventType = CandidateTestEventType.ADVANCE_TEST_PART;
}
else {
/* No more test parts.
*
* For single part tests, we terminate the test completely now as the test feedback was shown with the testPart feedback.
* For multi-part tests, we shall keep the test open so that the test feedback can be viewed.
*/
if (testSessionState.getTestPlan().getTestPartNodes().size()==1) {
eventType = CandidateTestEventType.EXIT_TEST;
testSessionController.exitTest(currentTimestamp);
//candidateSession.setTerminationTime(currentTimestamp);
//candidateSessionDao.update(candidateSession);
}
else {
eventType = CandidateTestEventType.ADVANCE_TEST_PART;
}
}
/* Record current result state */
//candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController);
/* Record and log event */
//final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession,
// eventType, testSessionState, notificationRecorder);
//candidateAuditLogger.logCandidateEvent(candidateTestEvent);
/*
String redirect;
if (candidateSession.isTerminated()) {
// We exited the test
//TODO fire event eXIT
redirect = redirectToExitUrl(candidateSessionContext, xsrfToken);
}
else {
// Moved onto next part
redirect = redirectToRenderSession(xid, xsrfToken);
}
*/
}
private void doReviewTestPart() {
//Assert.notNull(candidateSessionContext, "candidateSessionContext");
//assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST);
//final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
/* 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();
/* 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;
}
/* Record and log event */
//final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession,
// CandidateTestEventType.REVIEW_TEST_PART, null, null, testSessionState, notificationRecorder);
//candidateAuditLogger.logCandidateEvent(candidateTestEvent);
}
/**
* Exit multi-part tests
*/
private void doExitTest() {
//Assert.notNull(candidateSessionContext, "candidateSessionContext");
//assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST);
//final CandidateSession candidateSession = candidateSessionContext.getCandidateSession();
/* 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();
/* Perform action */
final Date currentTimestamp = requestTimestampContext.getCurrentRequestTimestamp();
try {
testSessionController.exitTest(currentTimestamp);
} catch (final QtiCandidateStateException e) {
//candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_EXIT_TEST);
logError("CANNOT_EXIT_TEST", null);
return;
} catch (final RuntimeException e) {
logError("Exploded", null);
return;// handleExplosion(e, candidateSession);
}
/* Update CandidateSession as appropriate */
//candidateSession.setTerminationTime(currentTimestamp);
//candidateSessionDao.update(candidateSession);
/* Record current result state (final) */
//candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController);
/* Record and log event */
//final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession,
// CandidateTestEventType.EXIT_TEST, testSessionState, notificationRecorder);
//candidateAuditLogger.logCandidateEvent(candidateTestEvent);
}
//private CandidateSession enterCandidateSession(final CandidateSession candidateSession) //private CandidateSession enterCandidateSession(final CandidateSession candidateSession)
private TestSessionController enterSession() { private TestSessionController enterSession() {
/* Set up listener to record any notifications */ /* Set up listener to record any notifications */
...@@ -329,4 +625,23 @@ public class QTI21DisplayController extends FormBasicController { ...@@ -329,4 +625,23 @@ public class QTI21DisplayController extends FormBasicController {
final int requestedLimitIntValue = requestedLimit.intValue(); final int requestedLimitIntValue = requestedLimit.intValue();
return requestedLimitIntValue > 0 ? requestedLimitIntValue : JqtiPlus.DEFAULT_TEMPLATE_PROCESSING_LIMIT; return requestedLimitIntValue > 0 ? requestedLimitIntValue : JqtiPlus.DEFAULT_TEMPLATE_PROCESSING_LIMIT;
} }
private class CandidateSessionFinisher {
public void finishCandidateSession(/* final CandidateSession candidateSession,*/ final AssessmentResult assessmentResult) {
/* Mark session as finished */
//candidateSession.setFinishTime(requestTimestampContext.getCurrentRequestTimestamp());
/* Also nullify LIS result info for session. These will be updated later, if pre-conditions match for sending the result back */
//candidateSession.setLisOutcomeReportingStatus(null);
//candidateSession.setLisScore(null);
// candidateSessionDao.update(candidateSession);
/* Finally schedule LTI result return (if appropriate and sane) */
//maybeScheduleLtiOutcomes(candidateSession, assessmentResult);
}
}
} }
\ No newline at end of file
...@@ -113,6 +113,17 @@ public class QTI21FormItem extends FormItemImpl { ...@@ -113,6 +113,17 @@ public class QTI21FormItem extends FormItemImpl {
String sub = uri.substring(selectItem.getPath().length()); String sub = uri.substring(selectItem.getPath().length());
QTIWorksEvent event = new QTIWorksEvent(selectItem, sub, this); QTIWorksEvent event = new QTIWorksEvent(selectItem, sub, this);
getRootForm().fireFormEvent(ureq, event); getRootForm().fireFormEvent(ureq, event);
} else if(uri.startsWith(finishItem.getPath())) {
QTIWorksEvent event = new QTIWorksEvent(finishItem, this);
getRootForm().fireFormEvent(ureq, event);
} else if(uri.startsWith(reviewItem.getPath())) {
String sub = uri.substring(reviewItem.getPath().length());
QTIWorksEvent event = new QTIWorksEvent(reviewItem, sub, this);
getRootForm().fireFormEvent(ureq, event);
} else if(uri.startsWith(itemSolution.getPath())) {
String sub = uri.substring(itemSolution.getPath().length());
QTIWorksEvent event = new QTIWorksEvent(itemSolution, sub, this);
getRootForm().fireFormEvent(ureq, event);
} else if(uri.startsWith(testPartNavigation.getPath())) { } else if(uri.startsWith(testPartNavigation.getPath())) {
QTIWorksEvent event = new QTIWorksEvent(testPartNavigation, this); QTIWorksEvent event = new QTIWorksEvent(testPartNavigation, this);
getRootForm().fireFormEvent(ureq, event); getRootForm().fireFormEvent(ureq, event);
...@@ -124,6 +135,16 @@ public class QTI21FormItem extends FormItemImpl { ...@@ -124,6 +135,16 @@ public class QTI21FormItem extends FormItemImpl {
} else if(uri.startsWith(endTestPart.getPath())) { } else if(uri.startsWith(endTestPart.getPath())) {
QTIWorksEvent event = new QTIWorksEvent(endTestPart, this); QTIWorksEvent event = new QTIWorksEvent(endTestPart, this);
getRootForm().fireFormEvent(ureq, event); getRootForm().fireFormEvent(ureq, event);
} else if(uri.startsWith(advanceTestPart.getPath())) {
QTIWorksEvent event = new QTIWorksEvent(advanceTestPart, this);
getRootForm().fireFormEvent(ureq, event);
} else if(uri.startsWith(reviewTestPart.getPath())) {
QTIWorksEvent event = new QTIWorksEvent(reviewTestPart, this);
getRootForm().fireFormEvent(ureq, event);
} else if(uri.startsWith(exitTest.getPath())) {
QTIWorksEvent event = new QTIWorksEvent(exitTest, this);
getRootForm().fireFormEvent(ureq, event);
} }
} }
......
...@@ -18,6 +18,40 @@ public class QTIWorksEvent extends FormEvent { ...@@ -18,6 +18,40 @@ public class QTIWorksEvent extends FormEvent {
private static final long serialVersionUID = 7767258131971848645L; private static final long serialVersionUID = 7767258131971848645L;
public enum Event {
source("source", "source"),
state("state", "state"),
result("result", "result"),
validation("validation", "validation"),
authorview("author-view", "author-view"),
response("response", "response"),//OK
testPartNavigation("test-part-navigation", "test-part-navigation"),//OK
selectItem("select-item", "select-item/"),//OK
finishItem("finish-item", "finish-item"),//impl
reviewTestPart("review-test-part", "review-test-part"),//impl
reviewItem("review-item", "review-item/"),//impl
itemSolution("item-solution", "item-solution/"),
endTestPart("end-test-part", "end-test-part"),//OK
advanceTestPart("advance-test-part", "advance-test-part"),//OK
exitTest("exit-test", "exit-test");//impl
private final String path;
private final String event;
private Event(String event, String path) {
this.event = event;
this.path = path;
}
public String getPath() {
return path;
}
public String event() {
return event;
}
}
private final Event event; private final Event event;
private final String subCommand; private final String subCommand;
private final Map<Identifier, StringResponseData> stringResponseMap; private final Map<Identifier, StringResponseData> stringResponseMap;
...@@ -53,38 +87,5 @@ public class QTIWorksEvent extends FormEvent { ...@@ -53,38 +87,5 @@ public class QTIWorksEvent extends FormEvent {
return stringResponseMap; return stringResponseMap;
} }
public enum Event {
source("source", "source"),
state("state", "state"),
result("result", "result"),
validation("validation", "validation"),
authorview("author-view", "author-view"),
response("response", "response"),//OK
testPartNavigation("test-part-navigation", "test-part-navigation"),//OK
selectItem("select-item", "select-item/"),//OK
finishItem("finish-item", "finish-item"),
reviewTestPart("review-test-part", "review-test-part"),
reviewItem("review-item", "review-item"),
itemSolution("item-solution", "item-solution"),
endTestPart("end-test-part", "end-test-part"),//OK
advanceTestPart("advance-test-part", "advance-test-part"),
exitTest("exit-test", "exit-test")
;
private final String path;
private final String event;
private Event(String event, String path) {
this.event = event;
this.path = path;
}
public String getPath() {
return path;
}
public String event() {
return event;
}
}
} }
<h1>Run</h1>
$r.render("qtirun") $r.render("qtirun")
\ No newline at end of file
create table o_qti_test_session (
id bigint not null,
creationdate datetime not null,
lastmodified datetime not null,
q_exploded bit not null default 0,
q_author_mode bit not null default 0,
q_finish_time datetime,
q_termination_time datetime,
fk_identity bigint not null,
fk_entry bigint not null,
fk_course bigint,
primary key (id)
);
alter table o_qti_test_session ENGINE = InnoDB;
alter table o_qti_test_session add constraint qti_sess_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
alter table o_qti_test_session add constraint qti_sess_to_course_entry_idx foreign key (fk_course) references o_repositoryentry (repositoryentry_id);
alter table o_qti_test_session add constraint qti_sess_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
create table o_qti_test_session (
id int8 not null,
creationdate timestamp not null,
lastmodified timestamp not null,
q_exploded bool default false,
q_author_mode bool default false,
q_finish_time timestamp,
q_termination_time timestamp,
fk_identity int8 not null,
fk_entry int8 not null,
fk_course int8,
primary key (id)
);
alter table o_qti_test_session add constraint qti_sess_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
create index idx_testess_to_repo_entry_idx on o_qti_test_session (fk_entry);
alter table o_qti_test_session add constraint qti_sess_to_course_entry_idx foreign key (fk_course) references o_repositoryentry (repositoryentry_id);
create index idx_qti_sess_to_course_entry_idx on o_qti_test_session (fk_course);
alter table o_qti_test_session add constraint qti_sess_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
create index idx_qti_sess_to_identity_idx on o_qti_test_session (fk_identity);
\ No newline at end of file
...@@ -108,7 +108,7 @@ Renders the test(Part) feedback ...@@ -108,7 +108,7 @@ Renders the test(Part) feedback
<xsl:if test="$currentTestPart/@navigationMode='nonlinear' or exists($itemSessionState/@entryTime)"> <xsl:if test="$currentTestPart/@navigationMode='nonlinear' or exists($itemSessionState/@entryTime)">
<li class="assessmentItem"> <li class="assessmentItem">
<form action="{$webappContextPath}{$reviewTestItemUrl}/{@key}" method="post"> <form action="{$webappContextPath}{$reviewTestItemUrl}/{@key}" method="post">
<button type="submit"> <button type="submit" class="btn btn-default">
<xsl:if test="not($reviewable)"> <xsl:if test="not($reviewable)">
<xsl:attribute name="disabled" select="'disabled'"/> <xsl:attribute name="disabled" select="'disabled'"/>
</xsl:if> </xsl:if>
......
...@@ -89,7 +89,7 @@ Renders the navigation for the current testPart ...@@ -89,7 +89,7 @@ Renders the navigation for the current testPart
<xsl:variable name="itemSessionState" select="$testSessionState/qw:item[@key=current()/@key]/qw:itemSessionState" as="element(qw:itemSessionState)"/> <xsl:variable name="itemSessionState" select="$testSessionState/qw:item[@key=current()/@key]/qw:itemSessionState" as="element(qw:itemSessionState)"/>
<li class="assessmentItem"> <li class="assessmentItem">
<form action="{$webappContextPath}{$selectTestItemUrl}/{@key}" method="post"> <form action="{$webappContextPath}{$selectTestItemUrl}/{@key}" method="post">
<button type="submit"> <button type="submit" class="btn btn-default">
<span class="questionTitle"><xsl:value-of select="@sectionPartTitle"/></span> <span class="questionTitle"><xsl:value-of select="@sectionPartTitle"/></span>
<xsl:apply-templates select="$itemSessionState" mode="item-status"/> <xsl:apply-templates select="$itemSessionState" mode="item-status"/>
</button> </button>
......
package org.olat.ims.qti21.manager;
import org.junit.Assert;
import org.junit.Test;
import org.olat.core.commons.persistence.DB;
import org.olat.core.id.Identity;
import org.olat.ims.qti21.UserTestSession;
import org.olat.repository.RepositoryEntry;
import org.olat.test.JunitTestHelper;
import org.olat.test.OlatTestCase;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 12.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class TestSessionDAOTest extends OlatTestCase {
@Autowired
private DB dbInstance;
@Autowired
private TestSessionDAO testSessionDao;
@Test
public void createTestSession() {
// prepare a test and a user
RepositoryEntry testEntry = JunitTestHelper.createAndPersistRepositoryEntry();
Identity assessedIdentity = JunitTestHelper.createAndPersistIdentityAsRndUser("test-session-1");
dbInstance.commit();
UserTestSession testSession = testSessionDao.createTestSession(testEntry, null, assessedIdentity);
Assert.assertNotNull(testSession);
dbInstance.commit();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment