From 83a2e95d9de211bd53634ba451774f942255c8a2 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 27 Apr 2012 10:59:57 +0200 Subject: [PATCH] OO-219: implements the possibility for tutors to pull an unfinished test --- .../ims/qti/QTIResultDetailsController.java | 2 +- .../gui/components/table/TableDataModel.java | 14 +-- .../nodes/iq/IQControllerCreatorOlat.java | 2 +- .../olat/course/nodes/iq/IQRunController.java | 46 +++++---- .../nodes/iq/_i18n/LocalStrings_de.properties | 1 + .../nodes/iq/_i18n/LocalStrings_en.properties | 1 + .../ims/qti/QTIResultDetailsController.java | 99 +++++++++++++++++-- .../org/olat/ims/qti/QTIResultManager.java | 2 +- .../org/olat/ims/qti/QTIResultTableModel.java | 66 +++++++++++-- .../ims/qti/QTISelectColumnDescriptor.java | 67 +++++++++++++ .../ims/qti/_i18n/LocalStrings_de.properties | 6 ++ .../ims/qti/_i18n/LocalStrings_en.properties | 6 ++ .../olat/ims/qti/process/FilePersister.java | 37 +++++-- .../org/olat/ims/qti/process/Persister.java | 9 ++ .../olat/modules/iq/IQDisplayController.java | 53 ++++++++-- .../java/org/olat/modules/iq/IQManager.java | 12 +-- .../org/olat/modules/iq/IQRetrievedEvent.java | 79 +++++++++++++++ 17 files changed, 432 insertions(+), 70 deletions(-) create mode 100644 src/main/java/org/olat/ims/qti/QTISelectColumnDescriptor.java create mode 100644 src/main/java/org/olat/modules/iq/IQRetrievedEvent.java diff --git a/src/main/java/de/bps/ims/qti/QTIResultDetailsController.java b/src/main/java/de/bps/ims/qti/QTIResultDetailsController.java index 6e3e2c3f833..462c8ef8ed0 100644 --- a/src/main/java/de/bps/ims/qti/QTIResultDetailsController.java +++ b/src/main/java/de/bps/ims/qti/QTIResultDetailsController.java @@ -126,7 +126,7 @@ public class QTIResultDetailsController extends BasicController { QTIResultManager qrm = QTIResultManager.getInstance(); tableModel = new QTIResultTableModel( - qrm.getResultSets(courseResourceableId, nodeIdent, repositoryEntry.getKey(), identity)); + qrm.getResultSets(courseResourceableId, nodeIdent, repositoryEntry.getKey(), identity), getTranslator()); tableCtr.setTableDataModel(tableModel); listenTo(tableCtr); diff --git a/src/main/java/org/olat/core/gui/components/table/TableDataModel.java b/src/main/java/org/olat/core/gui/components/table/TableDataModel.java index ad4b6f53d16..e6012259287 100644 --- a/src/main/java/org/olat/core/gui/components/table/TableDataModel.java +++ b/src/main/java/org/olat/core/gui/components/table/TableDataModel.java @@ -33,29 +33,29 @@ import java.util.List; * * @author Felix Jost */ -public interface TableDataModel { +public interface TableDataModel<T> { /** * @return */ - int getColumnCount(); + public int getColumnCount(); /** * @return */ - int getRowCount(); + public int getRowCount(); /** * @param row * @param col * @return */ - Object getValueAt(int row, int col); + public Object getValueAt(int row, int col); - Object getObject(int row); + public T getObject(int row); - void setObjects(List objects); + public void setObjects(List<T> objects); - Object createCopyWithEmptyList(); + public Object createCopyWithEmptyList(); } \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/iq/IQControllerCreatorOlat.java b/src/main/java/org/olat/course/nodes/iq/IQControllerCreatorOlat.java index 1ae8e535a77..6e8e6ed0d04 100644 --- a/src/main/java/org/olat/course/nodes/iq/IQControllerCreatorOlat.java +++ b/src/main/java/org/olat/course/nodes/iq/IQControllerCreatorOlat.java @@ -195,7 +195,7 @@ public class IQControllerCreatorOlat implements IQControllerCreator { public Controller createIQTestDetailsEditController(Long courseResourceableId, String ident, Identity identity, RepositoryEntry referencedRepositoryEntry, String qmdEntryTypeAssess, UserRequest ureq, WindowControl wControl) { - return new QTIResultDetailsController(courseResourceableId, ident, identity, referencedRepositoryEntry, qmdEntryTypeAssess, ureq, wControl); + return new QTIResultDetailsController(ureq, wControl, courseResourceableId, ident, identity, referencedRepositoryEntry, qmdEntryTypeAssess); } diff --git a/src/main/java/org/olat/course/nodes/iq/IQRunController.java b/src/main/java/org/olat/course/nodes/iq/IQRunController.java index b64208fd9a9..81f6d84dbef 100644 --- a/src/main/java/org/olat/course/nodes/iq/IQRunController.java +++ b/src/main/java/org/olat/course/nodes/iq/IQRunController.java @@ -348,10 +348,10 @@ public class IQRunController extends BasicController implements GenericEventList } } - private List allChats; private void checkChats (UserRequest ureq) { + List<?> allChats = null; if (ureq != null) { - allChats = (List) ureq.getUserSession().getEntry("chats"); + allChats = (List<?>)ureq.getUserSession().getEntry("chats"); } if (allChats == null || allChats.size() == 0) { startButton.setEnabled (true); @@ -395,7 +395,6 @@ public class IQRunController extends BasicController implements GenericEventList listenTo(displayController); if(displayController.isClosed()) { //do nothing - System.out.println(); } else if (displayController.isReady()) { // in case displayController was unable to initialize, a message was set by displayController // this is the case if no more attempts or security check was unsuccessfull @@ -508,27 +507,32 @@ public class IQRunController extends BasicController implements GenericEventList else if(type.equals(AssessmentInstance.QMD_ENTRY_TYPE_SELF)){ am.incrementNodeAttempts(courseNode, urequest.getIdentity(), userCourseEnv); } + } else if (event.equals(Event.DONE_EVENT)) { + stopAssessment(urequest, event); + } else if ("test_stopped".equals(event.getCommand())) { + stopAssessment(urequest, event); + showWarning("error.assessment.stopped"); } - else if (event.equals(Event.DONE_EVENT)) { - if(displayContainerController != null) { - displayContainerController.deactivate(urequest); - } else { - getWindowControl().pop(); - } - removeHistory(urequest); - OLATResourceable ores = OresHelper.createOLATResourceableInstance("test", -1l); - addToHistory(urequest, ores, null); - if (type.equals(AssessmentInstance.QMD_ENTRY_TYPE_ASSESS) && !assessmentStopped ) { - assessmentStopped = true; - AssessmentEvent assessmentStoppedEvent = new AssessmentEvent(AssessmentEvent.TYPE.STOPPED, userSession); - singleUserEventCenter.deregisterFor(this, assessmentInstanceOres); - singleUserEventCenter.fireEventToListenersOf(assessmentStoppedEvent, assessmentEventOres); - } - fireEvent(urequest, Event.DONE_EVENT); - - } } } + + private void stopAssessment(UserRequest ureq, Event event) { + if(displayContainerController != null) { + displayContainerController.deactivate(ureq); + } else { + getWindowControl().pop(); + } + removeHistory(ureq); + OLATResourceable ores = OresHelper.createOLATResourceableInstance("test", -1l); + addToHistory(ureq, ores, null); + if (type.equals(AssessmentInstance.QMD_ENTRY_TYPE_ASSESS) && !assessmentStopped ) { + assessmentStopped = true; + AssessmentEvent assessmentStoppedEvent = new AssessmentEvent(AssessmentEvent.TYPE.STOPPED, userSession); + singleUserEventCenter.deregisterFor(this, assessmentInstanceOres); + singleUserEventCenter.fireEventToListenersOf(assessmentStoppedEvent, assessmentEventOres); + } + fireEvent(ureq, event); + } private void exposeUserTestDataToVC(UserRequest ureq) { // config : show score info diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties index 4a89b2fd5e3..47158dee294 100644 --- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties @@ -136,6 +136,7 @@ error.surv.undefined.long=Es ist kein Fragebogen f\u00FCr "{0}" definiert. W\u00 error.surv.undefined.short=Es ist kein Fragebogen f\u00FCr "{0}" definiert. error.test.undefined.long=Es ist kein Test f\u00FCr "{0}" definiert. W\u00E4hlen Sie einen unter "Test-Konfiguration" aus. error.test.undefined.short=Es ist kein Test f\u00FCr "{0}" definiert. +error.assessment.stopped=Das Test wurde von Ihrem Tutor eingezogen. fieldset.chosecreateeditfile=Informationstext (HTML-Seite) file.name=Gew\u00E4hlte Datei help.iq.layout=Hilfe zu den Darstellungsoptionen diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties index 8e37b0a3768..546eeaf454a 100644 --- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties @@ -102,6 +102,7 @@ error.surv.undefined.long=No questionnaire for "{0}" defined. Choose one in the error.surv.undefined.short=No questionnaire for "{0}" defined. error.test.undefined.long=No test for "{0}" defined. Choose one in the section "Test configuration". error.test.undefined.short=No test for "{0}" defined. +error.assessment.stopped=Your tutor pull your test. fieldset.chosecreateeditfile=Information (HTML page) file.name=Selected file help.iq.file.name-self.hover=Help to select a self-test diff --git a/src/main/java/org/olat/ims/qti/QTIResultDetailsController.java b/src/main/java/org/olat/ims/qti/QTIResultDetailsController.java index 276169d8f64..233b2318358 100644 --- a/src/main/java/org/olat/ims/qti/QTIResultDetailsController.java +++ b/src/main/java/org/olat/ims/qti/QTIResultDetailsController.java @@ -25,11 +25,13 @@ package org.olat.ims.qti; +import java.io.File; +import java.util.List; + import org.dom4j.Document; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.table.DefaultColumnDescriptor; -import org.olat.core.gui.components.table.StaticColumnDescriptor; import org.olat.core.gui.components.table.TableController; import org.olat.core.gui.components.table.TableEvent; import org.olat.core.gui.components.table.TableGuiConfiguration; @@ -39,10 +41,28 @@ 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.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.i18n.I18nModule; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.assessment.AssessmentHelper; +import org.olat.course.nodes.AssessableCourseNode; +import org.olat.course.run.scoring.ScoreEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.ims.qti.container.AssessmentContext; +import org.olat.ims.qti.process.AssessmentFactory; +import org.olat.ims.qti.process.AssessmentInstance; import org.olat.ims.qti.process.FilePersister; +import org.olat.ims.qti.process.Persister; import org.olat.ims.qti.render.LocalizedXSLTransformer; +import org.olat.modules.ModuleConfiguration; +import org.olat.modules.iq.IQManager; +import org.olat.modules.iq.IQRetrievedEvent; import org.olat.repository.RepositoryEntry; +import org.olat.user.UserManager; /** * Initial Date: 12.01.2005 @@ -53,13 +73,18 @@ public class QTIResultDetailsController extends BasicController { private Long courseResourceableId; private String nodeIdent; - private Identity identity; + private Identity assessedIdentity; private RepositoryEntry repositoryEntry; + private Persister qtiPersister; private String type; + + private final IQManager iqm; + private final QTIResultManager qrm; private VelocityContainer main, details; private QTIResultTableModel tableModel; private TableController tableCtr; + private DialogBoxController retrieveConfirmationCtr; private CloseableModalController cmc; @@ -72,13 +97,20 @@ public class QTIResultDetailsController extends BasicController { * @param ureq * @param wControl */ - public QTIResultDetailsController(Long courseResourceableId, String nodeIdent, Identity identity, RepositoryEntry re, String type, UserRequest ureq, WindowControl wControl) { + public QTIResultDetailsController(UserRequest ureq, WindowControl wControl, Long courseResourceableId, String nodeIdent, Identity assessedIdentity, + RepositoryEntry re, String type) { super(ureq, wControl); this.courseResourceableId = courseResourceableId; this.nodeIdent = nodeIdent; - this.identity = identity; + this.assessedIdentity = assessedIdentity; this.repositoryEntry = re; this.type = type; + this.iqm = IQManager.getInstance(); + this.qrm = QTIResultManager.getInstance(); + + String resourcePath = courseResourceableId + File.separator + nodeIdent; + qtiPersister = new FilePersister(assessedIdentity, resourcePath); + System.out.println("qti.ser: " + qtiPersister.exists()); init(ureq); } @@ -92,11 +124,10 @@ public class QTIResultDetailsController extends BasicController { tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("column.header.date", 0, null, ureq.getLocale())); tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("column.header.duration", 1, null, ureq.getLocale())); tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("column.header.assesspoints", 2, null, ureq.getLocale())); - tableCtr.addColumnDescriptor(new StaticColumnDescriptor("sel", "column.header.details", getTranslator().translate("select"))); + tableCtr.addColumnDescriptor(new QTISelectColumnDescriptor("column.header.action", 3, ureq.getLocale(), getTranslator())); - QTIResultManager qrm = QTIResultManager.getInstance(); - tableModel = new QTIResultTableModel( - qrm.getResultSets(courseResourceableId, nodeIdent, repositoryEntry.getKey(), identity)); + List<QTIResultSet> resultSets = qrm.getResultSets(courseResourceableId, nodeIdent, repositoryEntry.getKey(), assessedIdentity); + tableModel = new QTIResultTableModel(resultSets, qtiPersister, getTranslator()); tableCtr.setTableDataModel(tableModel); listenTo(tableCtr); @@ -107,6 +138,7 @@ public class QTIResultDetailsController extends BasicController { /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) */ + @Override public void event(UserRequest ureq, Component source, Event event) { if (source == main) { if (event.getCommand().equals("close")) { @@ -118,13 +150,14 @@ public class QTIResultDetailsController extends BasicController { /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) */ + @Override public void event(UserRequest ureq, Controller source, Event event) { if (source == tableCtr) { TableEvent tEvent = (TableEvent)event; if (tEvent.getActionId().equals("sel")) { - QTIResultSet resultSet = tableModel.getResultSet(tEvent.getRowId()); + QTIResultSet resultSet = tableModel.getObject(tEvent.getRowId()); - Document doc = FilePersister.retreiveResultsReporting(identity, type, resultSet.getAssessmentID()); + Document doc = FilePersister.retreiveResultsReporting(assessedIdentity, type, resultSet.getAssessmentID()); if (doc == null) { showInfo("error.resreporting.na"); return; @@ -137,7 +170,20 @@ public class QTIResultDetailsController extends BasicController { listenTo(cmc); cmc.activate(); + } else if(tEvent.getActionId().equals("ret")) { + String fullname = UserManager.getInstance().getUserDisplayName(assessedIdentity.getUser()); + String title = translate("retrievetest.confirm.title"); + String text = translate("retrievetest.confirm.text", new String[]{fullname}); + retrieveConfirmationCtr = activateYesNoDialog(ureq, title, text, retrieveConfirmationCtr); } + } else if (source == retrieveConfirmationCtr) { + if(DialogBoxUIFactory.isYesEvent(event)) { + IQRetrievedEvent retrieveEvent = new IQRetrievedEvent(assessedIdentity, courseResourceableId, nodeIdent); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(retrieveEvent, retrieveEvent); + doRetrieveTest(ureq); + } + removeAsListenerAndDispose(retrieveConfirmationCtr); + retrieveConfirmationCtr = null; } } @@ -147,5 +193,38 @@ public class QTIResultDetailsController extends BasicController { protected void doDispose() { // } + + /** + * Retrieve the test: load the course, close the assessment instamce, persist the QTI + * result set, pass the score to the course node. + * @param ureq + */ + protected void doRetrieveTest(UserRequest ureq2) { + ICourse course = CourseFactory.loadCourse(courseResourceableId); + AssessableCourseNode testNode = (AssessableCourseNode)course.getRunStructure().getNode(nodeIdent); + ModuleConfiguration modConfig = testNode.getModuleConfiguration(); + + String resourcePathInfo = courseResourceableId + File.separator + nodeIdent; + AssessmentInstance ai = AssessmentFactory.createAssessmentInstance(assessedIdentity, modConfig, false ,resourcePathInfo); + //close the test + ai.close(); + //persist the results + iqm.persistResults(ai, courseResourceableId.longValue(), nodeIdent, assessedIdentity, ""); + //reporting + Document docResReporting = iqm.getResultsReporting(ai, assessedIdentity, I18nModule.getDefaultLocale()); + FilePersister.createResultsReporting(docResReporting, assessedIdentity, ai.getFormattedType(), ai.getAssessID()); + + //olat results + AssessmentContext ac = ai.getAssessmentContext(); + Float score = new Float(ac.getScore()); + Boolean passed = new Boolean(ac.isPassed()); + ScoreEvaluation sceval = new ScoreEvaluation(score, passed, new Long(ai.getAssessID())); + UserCourseEnvironment userCourseEnv = AssessmentHelper.createAndInitUserCourseEnvironment(assessedIdentity, course); + testNode.updateUserScoreEvaluation(sceval, userCourseEnv, assessedIdentity, true); + + List<QTIResultSet> resultSets = qrm.getResultSets(courseResourceableId, nodeIdent, repositoryEntry.getKey(), assessedIdentity); + tableModel.setObjects(resultSets); + tableCtr.modelChanged(); + } } diff --git a/src/main/java/org/olat/ims/qti/QTIResultManager.java b/src/main/java/org/olat/ims/qti/QTIResultManager.java index afe6bd804c9..8d6eee9fb19 100644 --- a/src/main/java/org/olat/ims/qti/QTIResultManager.java +++ b/src/main/java/org/olat/ims/qti/QTIResultManager.java @@ -84,7 +84,7 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @param identity May be null * @return List of resultsets */ - public List getResultSets(Long olatResource, String olatResourceDetail, Long repositoryRef, Identity identity) { + public List<QTIResultSet> getResultSets(Long olatResource, String olatResourceDetail, Long repositoryRef, Identity identity) { Long olatRes = olatResource; String olatResDet = olatResourceDetail; Long repRef = repositoryRef; diff --git a/src/main/java/org/olat/ims/qti/QTIResultTableModel.java b/src/main/java/org/olat/ims/qti/QTIResultTableModel.java index 66cfcfc2a77..3eefec578e5 100644 --- a/src/main/java/org/olat/ims/qti/QTIResultTableModel.java +++ b/src/main/java/org/olat/ims/qti/QTIResultTableModel.java @@ -26,28 +26,45 @@ package org.olat.ims.qti; +import java.util.ArrayList; import java.util.List; -import org.olat.core.gui.components.table.BaseTableDataModelWithoutFilter; import org.olat.core.gui.components.table.TableDataModel; +import org.olat.core.gui.translator.Translator; import org.olat.core.util.Formatter; import org.olat.course.assessment.AssessmentHelper; +import org.olat.ims.qti.process.Persister; /** * Initial Date: 12.01.2005 * * @author Mike Stock */ -public class QTIResultTableModel extends BaseTableDataModelWithoutFilter implements TableDataModel { +public class QTIResultTableModel implements TableDataModel<QTIResultSet> { private static final int COLUMN_COUNT = 3; - private List resultSets; + private List<QTIResultSet> resultSets; + private Persister persister; + private final Translator translator; /** * @param resultSets */ - public QTIResultTableModel(List resultSets) { + public QTIResultTableModel(List<QTIResultSet> resultSets, Translator translator) { + this(resultSets, null, translator); + } + + /** + * @param resultSets + */ + public QTIResultTableModel(List<QTIResultSet> resultSets, Persister persister, Translator translator) { this.resultSets = resultSets; + this.persister = persister; + this.translator = translator; + } + + private boolean isTestReleased() { + return persister == null || !persister.exists(); } /** @@ -61,14 +78,38 @@ public class QTIResultTableModel extends BaseTableDataModelWithoutFilter impleme * @see org.olat.core.gui.components.table.TableDataModel#getRowCount() */ public int getRowCount() { - return resultSets.size(); + return resultSets.size() + (isTestReleased() ? 0 : 1); + } + + @Override + public QTIResultSet getObject(int row) { + return resultSets.get(row); + } + + @Override + public void setObjects(List<QTIResultSet> objects) { + this.resultSets = objects; + } + + @Override + public QTIResultTableModel createCopyWithEmptyList() { + return new QTIResultTableModel(new ArrayList<QTIResultSet>(), persister, translator); } /** * @see org.olat.core.gui.components.table.TableDataModel#getValueAt(int, int) */ public Object getValueAt(int row, int col) { - QTIResultSet resultSet = (QTIResultSet)resultSets.get(row); + if(!isTestReleased() && (row+1 == getRowCount())) { + switch (col) { + case 0: return persister.getLastModified(); + case 1: return translator.translate("notReleased"); + case 2: return translator.translate("open"); + case 3: return Boolean.FALSE; + default: return "error"; + } + } + QTIResultSet resultSet = getObject(row); switch (col) { case 0: return resultSet.getLastModified(); case 1: { @@ -80,6 +121,7 @@ public class QTIResultTableModel extends BaseTableDataModelWithoutFilter impleme } } case 2: return "" + AssessmentHelper.getRoundedScore(resultSet.getScore()); + case 3: return Boolean.TRUE; default: return "error"; } } @@ -91,5 +133,17 @@ public class QTIResultTableModel extends BaseTableDataModelWithoutFilter impleme public QTIResultSet getResultSet(int rowId) { return (QTIResultSet)resultSets.get(rowId); } + + public static class Wrapper { + + private QTIResultSet resultSet; + + public Wrapper(QTIResultSet resultSet) { + this.resultSet = resultSet; + } + public QTIResultSet getResultSet() { + return resultSet; + } + } } diff --git a/src/main/java/org/olat/ims/qti/QTISelectColumnDescriptor.java b/src/main/java/org/olat/ims/qti/QTISelectColumnDescriptor.java new file mode 100644 index 00000000000..bb22c129bac --- /dev/null +++ b/src/main/java/org/olat/ims/qti/QTISelectColumnDescriptor.java @@ -0,0 +1,67 @@ +/** + * <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.qti; + +import java.util.Locale; + +import org.olat.core.gui.components.table.DefaultColumnDescriptor; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.translator.Translator; + +/** + * + * Description:<br> + * + * <P> + * Initial Date: 11 oct. 2011 <br> + * + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class QTISelectColumnDescriptor extends DefaultColumnDescriptor { + + private final Translator translator; + + public QTISelectColumnDescriptor(final String headerKey, final int dataColumn, final Locale locale, final Translator translator) { + super(headerKey, dataColumn, null, locale); + this.translator = translator; + } + + @Override + public String getAction(int row) { + int sortedRow = table.getSortedRow(row); + Object state = getTable().getTableDataModel().getValueAt(sortedRow, getDataColumn()); + if(state instanceof Boolean && !((Boolean)state).booleanValue()) { + return "ret"; + } + return "sel"; + } + + @Override + public void renderValue(StringOutput sb, int row, Renderer renderer) { + int sortedRow = table.getSortedRow(row); + Object state = getTable().getTableDataModel().getValueAt(sortedRow, getDataColumn()); + if(state instanceof Boolean && ((Boolean)state).booleanValue()) { + sb.append(translator.translate("select")); + } else { + sb.append(translator.translate("retrievetest")); + } + } +} diff --git a/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_de.properties index f14411d71dd..cf176e01dbb 100644 --- a/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_de.properties @@ -22,10 +22,16 @@ ass.identifier=Identifikator (Matrikelnummer) ass.inst=Institution ass.title=Assessment ass.user=Testperson +column.header.action=Aktion column.header.assesspoints=Punkte column.header.date=Datum column.header.details=Details column.header.duration=Dauer +retrievetest=Einziehen +retrievetest.confirm.title=Test einziehen +retrievetest.confirm.text=Wollen Sie wirklich den Test von {0} einziehen? +notReleased=Nicht abgegeben +open=Offen date=Datum dur=Dauer editor.newquestion=Neue Frage diff --git a/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_en.properties index 620bd3fd228..f1ee9af447f 100644 --- a/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti/_i18n/LocalStrings_en.properties @@ -22,10 +22,16 @@ ass.identifier=Identification number (registration number) ass.inst=Institution ass.title=Assessment ass.user=Candidate +column.header.action=Action column.header.assesspoints=Score column.header.date=Date column.header.details=Details column.header.duration=Duration +retrievetest=Pull +retrievetest.confirm.title=Pull test +retrievetest.confirm.text=Do you really want to pull the test of {0}? +notReleased=Not released +open=Open date=Date days=Days dur=Duration diff --git a/src/main/java/org/olat/ims/qti/process/FilePersister.java b/src/main/java/org/olat/ims/qti/process/FilePersister.java index da5bb76da76..38d50860c0a 100644 --- a/src/main/java/org/olat/ims/qti/process/FilePersister.java +++ b/src/main/java/org/olat/ims/qti/process/FilePersister.java @@ -36,6 +36,7 @@ import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; +import java.util.Date; import org.dom4j.Document; import org.dom4j.Element; @@ -43,6 +44,7 @@ import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import org.olat.core.id.Identity; 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.WebappHelper; @@ -52,6 +54,8 @@ import org.olat.ims.resources.IMSEntityResolver; /** */ public class FilePersister implements Persister { + private static OLog log = Tracing.createLoggerFor(FilePersister.class); + private static final String QTI_SER = "qtiser"; private static final String RES_REPORTING = "resreporting"; private static final String QTI_FILE = "qti.ser"; @@ -72,6 +76,21 @@ public class FilePersister implements Persister { this.resourcePathInfo = resourcePathInfo; this.subjectName = subj.getName(); } + + @Override + public boolean exists() { + File fSerial = new File(getFullQtiPath(), QTI_FILE); + return fSerial.exists(); + } + + @Override + public Date getLastModified() { + File fSerial = new File(getFullQtiPath(), QTI_FILE); + if(fSerial.exists()) { + return new Date(fSerial.lastModified()); + } + return null; + } /** * serialize the current test in case of a stop and later resume (e.g. the @@ -80,12 +99,13 @@ public class FilePersister implements Persister { * * @see org.olat.ims.qti.process.Persister#persist(Object, String) */ + @Override public void persist(Object o, String info) { File fSerialDir = new File(getFullQtiPath()); OutputStream os = null; try { long start = -1; - boolean debugOn = Tracing.isDebugEnabled(FilePersister.class); + boolean debugOn = log.isDebug(); if (debugOn) { start = System.currentTimeMillis(); } @@ -102,7 +122,7 @@ public class FilePersister implements Persister { os.close(); if (debugOn) { long stop = System.currentTimeMillis(); - Tracing.logDebug("time in ms to save ims qti ser file:"+(stop-start),FilePersister.class); + log.debug("time in ms to save ims qti ser file:"+(stop-start)); } } catch (Exception e) { try { @@ -117,9 +137,10 @@ public class FilePersister implements Persister { /** * returns (at the moment) only AssessmentInstances, see persist() */ + @Override public Object toRAM() { // File path e.g. qtiser/<Unique_Course_ID>/<Node_ID>/test/qti.ser - File fSerialDir = new File( getFullQtiPath()); + File fSerialDir = new File( getFullQtiPath()); if ( !fSerialDir.exists() ) { // file not found => try older path version ( < V5.1) e.g. qtiser/test/360459/qti.ser String path = QTI_SER + File.separator + subjectName + File.separator + resourcePathInfo; @@ -129,7 +150,7 @@ public class FilePersister implements Persister { InputStream is = null; try { long start = -1; - boolean debugOn = Tracing.isDebugEnabled(FilePersister.class); + boolean debugOn = log.isDebug(); if (debugOn) { start = System.currentTimeMillis(); } @@ -141,7 +162,7 @@ public class FilePersister implements Persister { is.close(); if (debugOn) { long stop = System.currentTimeMillis(); - Tracing.logDebug("time in ms to load ims qti ser file:"+(stop-start),FilePersister.class); + log.debug("time in ms to load ims qti ser file:"+(stop-start)); } } catch (Exception e) { @@ -248,7 +269,7 @@ public class FilePersister implements Persister { File userDir = new File(subDirs[j],identity.getName()); if (userDir.exists()) { FileUtils.deleteDirsAndFiles(userDir, true, true); - Tracing.logDebug("Delete qti.ser Userdata dir=" + userDir.getAbsolutePath(), FilePersister.class); + log.debug("Delete qti.ser Userdata dir=" + userDir.getAbsolutePath()); } } } @@ -258,13 +279,13 @@ public class FilePersister implements Persister { File qtiserDir = new File(WebappHelper.getUserDataRoot() + File.separator + QTI_SER + File.separator + identity.getName()); if (qtiserDir != null) { FileUtils.deleteDirsAndFiles(qtiserDir, true, true); - Tracing.logDebug("Delete qti.ser Userdata dir=" + qtiserDir.getAbsolutePath(), FilePersister.class); + log.debug("Delete qti.ser Userdata dir=" + qtiserDir.getAbsolutePath()); } // 3. Delete resreporting @ /resreporting/<USER_NAME> File resReportingDir = new File(WebappHelper.getUserDataRoot() + File.separator + RES_REPORTING + File.separator + identity.getName()); if (resReportingDir != null) { FileUtils.deleteDirsAndFiles(resReportingDir, true, true); - Tracing.logDebug("Delete qti resreporting Userdata dir=" + qtiserDir.getAbsolutePath(), FilePersister.class); + log.debug("Delete qti resreporting Userdata dir=" + qtiserDir.getAbsolutePath()); } } catch (Exception e) { throw new OLATRuntimeException(FilePersister.class, "could not delete QTI resreporting dir for identity=" + identity, e); diff --git a/src/main/java/org/olat/ims/qti/process/Persister.java b/src/main/java/org/olat/ims/qti/process/Persister.java index b650e580341..b76ab1048bb 100644 --- a/src/main/java/org/olat/ims/qti/process/Persister.java +++ b/src/main/java/org/olat/ims/qti/process/Persister.java @@ -25,10 +25,19 @@ package org.olat.ims.qti.process; +import java.util.Date; + /** */ public interface Persister { + + public boolean exists(); + + public Date getLastModified(); + public void persist(Object o, String info); + public Object toRAM(); + public void cleanUp(); } diff --git a/src/main/java/org/olat/modules/iq/IQDisplayController.java b/src/main/java/org/olat/modules/iq/IQDisplayController.java index 3892d6b98d6..9d8403cc5c3 100644 --- a/src/main/java/org/olat/modules/iq/IQDisplayController.java +++ b/src/main/java/org/olat/modules/iq/IQDisplayController.java @@ -39,12 +39,14 @@ import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.progressbar.ProgressBar; import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.DefaultController; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; +import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; @@ -54,6 +56,8 @@ import org.olat.core.logging.activity.StringResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.resource.OresHelper; import org.olat.course.nodes.iq.IQEditController; import org.olat.ims.qti.QTIConstants; @@ -75,7 +79,7 @@ import org.olat.util.logging.activity.LoggingResourceable; /** * @author Felix Jost */ -public class IQDisplayController extends DefaultController implements Activateable2 { +public class IQDisplayController extends DefaultController implements GenericEventListener, Activateable2 { private static final String PACKAGE = Util.getPackageName(IQDisplayController.class); private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(IQDisplayController.class); @@ -88,6 +92,8 @@ public class IQDisplayController extends DefaultController implements Activateab private String repositorySoftkey = null; private Resolver resolver = null; private Persister persister = null; + private final Identity assessedIdentity; + private volatile boolean retrievedFlag = false; private ProgressBar qtiscoreprogress, qtiquestionprogress; private IQComponent qticomp; @@ -100,6 +106,7 @@ public class IQDisplayController extends DefaultController implements Activateab private String callingResDetail = ""; private boolean ready; private Link closeButton; + private OLATResourceable retrieveListenerOres; /** * IMS QTI Display Controller used by the course nodes @@ -118,6 +125,8 @@ public class IQDisplayController extends DefaultController implements Activateab IQDisplayController(ModuleConfiguration moduleConfiguration, IQSecurityCallback secCallback, UserRequest ureq, WindowControl wControl, long callingResId, String callingResDetail) { super(wControl); + + this.assessedIdentity = ureq.getIdentity(); ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_OPEN, getClass()); @@ -147,6 +156,7 @@ public class IQDisplayController extends DefaultController implements Activateab ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_OPEN, getClass()); + this.assessedIdentity = ureq.getIdentity(); this.modConfig = new ModuleConfiguration(); modConfig.set(IQEditController.CONFIG_KEY_ENABLEMENU, Boolean.TRUE); modConfig.set(IQEditController.CONFIG_KEY_TYPE, type); @@ -167,6 +177,9 @@ public class IQDisplayController extends DefaultController implements Activateab this.translator = new PackageTranslator(PACKAGE, ureq.getLocale()); this.ready = false; + retrieveListenerOres = new IQRetrievedEvent(ureq.getIdentity(), callingResId, callingResDetail); + CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, ureq.getIdentity(), retrieveListenerOres); + iqm = IQManager.getInstance(); myContent = new VelocityContainer("olatmodiqrun", VELOCITY_ROOT + "/qti.html", translator, this); @@ -371,12 +384,36 @@ public class IQDisplayController extends DefaultController implements Activateab } + @Override + public void event(Event event) { + if(event instanceof IQRetrievedEvent) { + IQRetrievedEvent e = (IQRetrievedEvent)event; + if(e.isConcerned(assessedIdentity, callingResId, callingResDetail)) { + //it's me -> it's finished + retrievedFlag = true; + } + } + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(retrievedFlag) { + fireEvent(ureq, new Event("test_stopped")); + } else { + super.event(ureq, source, event); + } + } + /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, * org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) */ public void event(UserRequest ureq, Component source, Event event) { - + if(retrievedFlag) { + fireEvent(ureq, new Event("test_stopped")); + return; + } + if (source == myContent || source == qticomp) { // those must be links String wfCommand = event.getCommand(); // process workflow @@ -491,7 +528,7 @@ public class IQDisplayController extends DefaultController implements Activateab */ protected void postSubmitAssessment(UserRequest ureq, AssessmentInstance ai) { if (!qtistatus.isPreview()) { - iqm.persistResults(ai, callingResId, callingResDetail, ureq); + iqm.persistResults(ai, callingResId, callingResDetail, ureq.getIdentity(), ureq.getHttpReq().getRemoteAddr()); getWindowControl().setInfo(translator.translate("status.results.saved")); } else { getWindowControl().setInfo(translator.translate("status.results.notsaved")); @@ -507,7 +544,7 @@ public class IQDisplayController extends DefaultController implements Activateab } protected void generateDetailsResults(UserRequest ureq, AssessmentInstance ai) { - Document docResReporting = iqm.getResultsReporting(ai, ureq); + Document docResReporting = iqm.getResultsReporting(ai, ureq.getIdentity(), ureq.getLocale()); if (!iqsec.isPreview()) { FilePersister.createResultsReporting(docResReporting, ureq.getIdentity(), ai.getFormattedType(), ai.getAssessID()); // Send score and passed to parent controller. Maybe it is necessary @@ -537,10 +574,10 @@ public class IQDisplayController extends DefaultController implements Activateab * @param ureq */ private void logAudit(UserRequest ureq) { - Set params = ureq.getParameterSet(); + Set<String> params = ureq.getParameterSet(); StringBuilder sb = new StringBuilder(); - for (Iterator iter = params.iterator(); iter.hasNext();) { - String paramName = (String) iter.next(); + for (Iterator<String> iter = params.iterator(); iter.hasNext();) { + String paramName = iter.next(); sb.append("|"); sb.append(paramName); sb.append("="); @@ -560,6 +597,6 @@ public class IQDisplayController extends DefaultController implements Activateab * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) */ protected void doDispose() { - // + CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, retrieveListenerOres); } } diff --git a/src/main/java/org/olat/modules/iq/IQManager.java b/src/main/java/org/olat/modules/iq/IQManager.java index 3b2e3697f46..e21fb5b107b 100644 --- a/src/main/java/org/olat/modules/iq/IQManager.java +++ b/src/main/java/org/olat/modules/iq/IQManager.java @@ -41,7 +41,6 @@ import org.hibernate.Hibernate; import org.hibernate.type.Type; import org.olat.admin.user.delete.service.UserDeletionManager; import org.olat.basesecurity.BaseSecurityManager; -import org.olat.basesecurity.IdentityManagerImpl; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.DBFactory; @@ -88,7 +87,6 @@ import org.olat.modules.ModuleConfiguration; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.user.UserDataDeletable; -import org.olat.user.UserManager; import org.olat.util.logging.activity.LoggingResourceable; /** @@ -229,9 +227,9 @@ public class IQManager extends BasicManager implements UserDataDeletable { * @param ureq * @return */ - public Document getResultsReporting(AssessmentInstance ai, UserRequest ureq) { + public Document getResultsReporting(AssessmentInstance ai, Identity assessedIdentity, Locale locale) { ResultsBuilder resB = new ResultsBuilder(); - return resB.getResDoc(ai, ureq.getLocale(), ureq.getIdentity()); + return resB.getResDoc(ai, locale, assessedIdentity); } /** @@ -319,7 +317,7 @@ public class IQManager extends BasicManager implements UserDataDeletable { * @param ureq */ - public void persistResults(AssessmentInstance ai, long resId, String resDetail, UserRequest ureq) { + public void persistResults(AssessmentInstance ai, long resId, String resDetail, Identity assessedIdentity, String remoteAddr) { AssessmentContext ac = ai.getAssessmentContext(); QTIResultSet qtiResultSet = new QTIResultSet(); @@ -327,7 +325,7 @@ public class IQManager extends BasicManager implements UserDataDeletable { qtiResultSet.setOlatResource(resId); qtiResultSet.setOlatResourceDetail(resDetail); qtiResultSet.setRepositoryRef(ai.getRepositoryEntryKey()); - qtiResultSet.setIdentity(ureq.getIdentity()); + qtiResultSet.setIdentity(assessedIdentity); qtiResultSet.setQtiType(ai.getType()); qtiResultSet.setAssessmentID(ai.getAssessID()); @@ -361,7 +359,7 @@ public class IQManager extends BasicManager implements UserDataDeletable { else qtiResult.setScore(ic.getScore()); qtiResult.setTstamp(new Date(ic.getLatestAnswerTime())); qtiResult.setLastModified(new Date(System.currentTimeMillis())); - qtiResult.setIp(ureq.getHttpReq().getRemoteAddr()); + qtiResult.setIp(remoteAddr); // Get user answers for this item StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/org/olat/modules/iq/IQRetrievedEvent.java b/src/main/java/org/olat/modules/iq/IQRetrievedEvent.java new file mode 100644 index 00000000000..5010732cb96 --- /dev/null +++ b/src/main/java/org/olat/modules/iq/IQRetrievedEvent.java @@ -0,0 +1,79 @@ +/** + * <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.modules.iq; + +import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.event.MultiUserEvent; + +/** + * + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class IQRetrievedEvent extends MultiUserEvent implements OLATResourceable { + + private static final long serialVersionUID = -4054560436205827712L; + + private Long assessedIdentityKey; + private Long courseResourceableId; + private String nodeIdent; + + /** + * constructor for a retrieved survey event + */ + public IQRetrievedEvent(Identity assessedIdentity, Long courseResourceableId, String nodeIdent) { + super("iqretrieved"); + this.assessedIdentityKey = assessedIdentity.getKey(); + this.courseResourceableId = courseResourceableId; + this.nodeIdent = nodeIdent; + } + + public Long getAssessedIdentityKey() { + return assessedIdentityKey; + } + + @Override + public String getResourceableTypeName() { + return "iqretrieved"; + } + + @Override + public Long getResourceableId() { + return assessedIdentityKey; + } + + public Long getCourseResourceableId() { + return courseResourceableId; + } + + public String getNodeIdent() { + return nodeIdent; + } + + public boolean isConcerned(Identity identity, Long courseResourceableId, String nodeIdent) { + if(identity != null && identity.getKey().equals(getAssessedIdentityKey()) + && getCourseResourceableId() != null && getCourseResourceableId().equals(courseResourceableId) + && getNodeIdent() != null && getNodeIdent().equals(nodeIdent)) { + //it's me -> it's finished + return true; + } + return false; + } +} -- GitLab