/**
* <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.scorm.archiver;
import java.util.List;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.filters.VFSItemFilter;
import org.olat.course.archiver.ScoreAccountingHelper;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.ScormCourseNode;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.modules.scorm.ScormDirectoryHelper;
import org.olat.modules.scorm.server.servermodels.ScoDocument;
public class ScormExportManager {
private static final String CMI_OBJECTIVES = "cmi.objectives.";
private static final String CMI_INTERACTIONS = "cmi.interactions.";
private static final String CMI_RAW_SCORE = "cmi.core.score.raw";
private static final String CMI_LESSON_STATUS = "cmi.core.lesson_status";
private static final String CMI_COMMENTS = "cmi.comments";
private static final String CMI_TOTAL_TIME = "cmi.core.total_time";
private static final String CMI_ID = "id";
private static final String CMI_SCORE_RAW = "score.raw";
private static final String CMI_SCORE_MIN = "score.min";
private static final String CMI_SCORE_MAX = "score.max";
private static final String CMI_RESULT = "result";
private static final String CMI_STUDENT_RESPONSE = "student_response";
private static final String CMI_CORRECT_RESPONSE = "correct_responses.";
private static final String OBJECTIVES = "objectives.";
private static final String CMI_COUNT = "_count";
private static final OLog logger = Tracing.createLoggerFor(ScormExportManager.class);
private static final ScormExportManager instance = new ScormExportManager();
private ScormExportManager(){
//
}
public static ScormExportManager getInstance() {
return instance;
}
/**
* Export the results of a SCORM course
* @param courseEnv
* @param node
* @param translator
* @param exportDirectory
* @param charset
* @return the name of the file, if any created or empty string otherwise
*/
public String getResults(CourseEnvironment courseEnv, ScormCourseNode node, Translator translator) {
ScormExportFormatter visitor = new ScormExportFormatter(translator);
visitScoDatas(courseEnv, node, visitor);
return visitor.export();
}
/**
* Finds out if any results available.
* @param courseEnv
* @param node
* @param translator
* @return
*/
public boolean hasResults(CourseEnvironment courseEnv, CourseNode node, Translator translator) {
ScormExportVisitor visitor = new ScormExportFormatter(translator);
boolean dataFound = visitScoDatas(courseEnv, (ScormCourseNode)node, visitor);
return dataFound;
}
/**
* Visit the scos user's datamodel of a SCORM course. The users must be in a group.
* @param courseEnv
* @param node
* @param visitor
*/
public boolean visitScoDatas(CourseEnvironment courseEnv, ScormCourseNode node, ScormExportVisitor visitor) {
boolean dataFound = false;
Long courseId = courseEnv.getCourseResourceableId();
String scoDirectoryName = courseId.toString() + "-" + node.getIdent();
VFSContainer scormRoot = ScormDirectoryHelper.getScormRootFolder();
List<Identity> users = ScoreAccountingHelper.loadUsers(courseEnv);
//fxdiff: FXOLAT-249 prevent connection timeout if collecting data take a long time
DBFactory.getInstance().commitAndCloseSession();
for (Identity identity : users) {
String username = identity.getName();
VFSItem userFolder = scormRoot.resolve(username);
if(userFolder instanceof VFSContainer) {
VFSItem scosFolder = ((VFSContainer)userFolder).resolve(scoDirectoryName);
if(scosFolder instanceof VFSContainer) {
collectData(username, (VFSContainer)scosFolder, visitor);
dataFound = true;
}
}
}
return dataFound;
}
private void collectData(String username, VFSContainer scoFolder, ScormExportVisitor visitor) {
List<VFSItem> contents = scoFolder.getItems(new XMLFilter());
for(VFSItem file:contents) {
ScoDocument document = new ScoDocument(null);
try {
if(file instanceof LocalFileImpl) {
document.loadDocument(((LocalFileImpl)file).getBasefile());
}
else {
logger.warn("Cannot use this type of VSFItem to load a SCO Datamodel: " + file.getClass().getName(), null);
continue;
}
String[][] scoModel = document.getScoModel();
ScoDatas parsedDatas = parseScoModel(file.getName(), username, scoModel);
visitor.visit(parsedDatas);
} catch (Exception e) {
logger.error("Cannot load a SCO Datamodel", e);
}
}
}
/**
* Parse the raw cmi datas in a java friendly object.
* @param scoId
* @param username
* @param scoModel
* @return
*/
private ScoDatas parseScoModel(String scoId, String username, String[][] scoModel) {
ScoDatas datas = new ScoDatas(scoId, username);
String curInteractionID = null;
for(String[] line:scoModel) {
String key = null;
try {
key = line[0];
if(key == null) continue;
String value = line[1];
if(key.equals(CMI_RAW_SCORE)) {
datas.setRawScore(value);
}
else if(key.equals(CMI_LESSON_STATUS)) {
datas.setLessonStatus(value);
}
else if(key.equals(CMI_COMMENTS)) {
datas.setComments(value);
}
else if(key.equals(CMI_TOTAL_TIME)) {
datas.setTotalTime(value);
}
else if(key.startsWith(CMI_OBJECTIVES)) {
String endStr = key.substring(CMI_OBJECTIVES.length());
int nextPoint = endStr.indexOf('.');
if(nextPoint < 0) {
//cmi.objectives._count
continue;
}
String interactionNr = endStr.substring(0, nextPoint);
int nr = Integer.valueOf(interactionNr).intValue();
ScoObjective objective = datas.getObjective(nr);
String endKey = endStr.substring(nextPoint + 1);
if(CMI_ID.equals(endKey)) {
objective.setId(value);
}
if(CMI_SCORE_RAW.equals(endKey)) {
objective.setScoreRaw(value);
}
else if(CMI_SCORE_MIN.equals(endKey)) {
objective.setScoreMin(value);
}
else if(CMI_SCORE_MAX.equals(endKey)) {
objective.setScoreMax(value);
}
}
else if(key.startsWith(CMI_INTERACTIONS)) {
String endStr = key.substring(CMI_INTERACTIONS.length());
int nextPoint = endStr.indexOf('.');
if(nextPoint < 0) {
continue;
}
String interactionNr = endStr.substring(0, nextPoint);
int nr = Integer.valueOf(interactionNr).intValue();
ScoInteraction interaction = datas.getInteraction(nr);
if (curInteractionID != null) {
interaction = datas.getInteractionByID (curInteractionID);
}
String endKey = endStr.substring(nextPoint + 1);
if(CMI_ID.equals(endKey)) {
interactionNr = endStr.substring(0, nextPoint);
nr = Integer.valueOf(interactionNr).intValue();
interaction = datas.getInteraction(nr);
curInteractionID = value;
interaction.setInteractionId(value);
}
else if(CMI_RESULT.equals(endKey)) {
interaction.setResult(value);
}
else if(CMI_STUDENT_RESPONSE.equals(endKey)) {
interaction.setStudentResponse(value);
}
else if(endKey.startsWith(CMI_CORRECT_RESPONSE)) {
interaction.setCorrectResponse(value);
}
else if(endKey.indexOf(OBJECTIVES) >= 0 && endKey.indexOf(CMI_COUNT) < 0) {
interaction.getObjectiveIds().add(value);
}
}
}
catch(Exception ex) {
logger.debug("Error parse this cmi data: " + key);
}
}
return datas;
}
public class XMLFilter implements VFSItemFilter {
public boolean accept(VFSItem file) {
String name = file.getName();
if(name.endsWith(".xml") && !(name.equals("reload-settings.xml")))
{
return true;
}
return false;
}
}
}