/**
* <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>
* BPS Bildungsportal Sachsen GmbH, http://www.bps-system.de
* <p>
*/
package de.bps.onyx.plugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.IOUtils;
import org.olat.core.configuration.AbstractSpringModule;
import org.olat.core.configuration.ConfigOnOff;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.PathUtils;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.course.nodes.QTICourseNode;
import org.olat.course.run.scoring.ScoreEvaluation;
import org.olat.fileresource.types.ResourceEvaluation;
import org.olat.ims.qti.QTIResultManager;
import org.olat.ims.qti.QTIResultSet;
import org.olat.ims.qti.fileresource.SurveyFileResource;
import org.olat.ims.qti.fileresource.TestFileResource;
import org.olat.ims.qti.process.ImsRepositoryResolver;
import org.olat.ims.qti.process.Resolver;
import org.olat.modules.assessment.model.AssessmentEntryStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @author Ingmar Kroll
*/
@Service("onyxModule")
public class OnyxModule extends AbstractSpringModule implements ConfigOnOff {
private static final OLog log = Tracing.createLoggerFor(OnyxModule.class);
@Value("${onyx.plugin.wslocation}")
private String onyxPluginWSLocation;
public static ArrayList<PlayerTemplate> PLAYERTEMPLATES;
/*
* holds the local config name which is sent to the remote onyxplugin -> onyxplugin must have a config corresponding to this name
*/
@Value("${onyx.plugin.configname}")
private String configName;
// <OLATCE-713>
@Value("${onyx.plugin.userviewlocation}")
private String onyxUserViewLocation;
@Value("${onyx.reporter.userviewlocation}")
private String onyxReporterUserViewLocation;
// </OLATCE-713>
@Value("${onyx.plugin.exammodelocation}")
private String onyxExamModeLocation;
@Value("${assessmentplugin.activate}")
private String assessmentPlugin;
private static Map<Long,Boolean> onyxMap = new ConcurrentHashMap<Long,Boolean>();
@Autowired
public OnyxModule(CoordinatorManager coordinatorManager) {
super(coordinatorManager);
}
@Override
public boolean isEnabled() {
return assessmentPlugin != null && "Onyx".equals(assessmentPlugin);
}
/**
* @return Returns the configName.
*/
public String getConfigName() {
return configName;
}
/**
* @param configName The configName to set.
*/
public void setConfigName(final String configName) {
this.configName = configName;
}
/**
* @param pluginWSLocation The pluginWSLocation to set.
*/
public void setOnyxPluginWSLocation(final String onyxPluginWSLocation) {
this.onyxPluginWSLocation = onyxPluginWSLocation;
}
/**
* @param onyxExamModeLocation
* The location of the onyx exam mode
*/
public void setOnyxExamModeLocation(String onyxExamModeLocation) {
this.onyxExamModeLocation = onyxExamModeLocation;
}
/**
* @return Returns the userViewLocation.
*/
public String getUserViewLocation() {
// <OLATCE-713>
return onyxUserViewLocation;
// </OLATCE-713>
}
/**
* @return Returns the pluginWSLocation.
*/
public String getPluginWSLocation() {
return onyxPluginWSLocation + "/services";
}
/**
* @return The location of the Onyx Exam Mode
*/
public String getOnyxExamModeLocation() {
return onyxExamModeLocation;
}
/**
* [user by Spring]
* @param assessmentPlugin
*/
public void setAssessmentPlugin(String assessmentPlugin) {
this.assessmentPlugin = assessmentPlugin;
}
@Override
public void init() {
PLAYERTEMPLATES = new ArrayList<PlayerTemplate>();
PlayerTemplate pt = new PlayerTemplate("onyxdefault", "templatewithtree");
PLAYERTEMPLATES.add(pt);
pt = new PlayerTemplate("onyxwithoutnav", "templatewithouttree");
PLAYERTEMPLATES.add(pt);
}
@Override
protected void initFromChangedProperties() {
//
}
public class PlayerTemplate {
public String id;
public String i18nkey;
/**
* @param id
* @param i18nkey
*/
public PlayerTemplate(final String id, final String i18nkey) {
this.id = id;
this.i18nkey = i18nkey;
}
}
public static boolean isOnyxTest(final OLATResourceable res) {
if (res.getResourceableTypeName().equals(TestFileResource.TYPE_NAME) ||
res.getResourceableTypeName().equals(SurveyFileResource.TYPE_NAME)) {
Long resourceId = res.getResourceableId();
Boolean onyx = onyxMap.get(resourceId);
if(onyx == null) {
onyx = Boolean.FALSE;
try {
final Resolver resolver = new ImsRepositoryResolver(res);
// search for qti.xml, it not exists for qti2
if (resolver.getQTIDocument() == null) {
onyx = Boolean.TRUE;
} else {
onyx = Boolean.FALSE;
}
} catch(OLATRuntimeException e) {
log.error("", e);
}
onyxMap.put(resourceId, onyx);
}
return onyx.booleanValue();
} else {
return false;
}
}
public static ResourceEvaluation isOnyxTest(File file, String filename) {
ResourceEvaluation eval = new ResourceEvaluation();
BufferedReader reader = null;
try {
ImsManifestFileFilter visitor = new ImsManifestFileFilter();
Path fPath = PathUtils.visit(file, filename, visitor);
if(visitor.isValid()) {
Path qtiPath = fPath.resolve("imsmanifest.xml");
reader = Files.newBufferedReader(qtiPath, StandardCharsets.UTF_8);
while (reader.ready()) {
String l = reader.readLine();
if (l.indexOf("imsqti_xmlv2p1") != -1 || l.indexOf("imsqti_test_xmlv2p1") != -1 || l.indexOf("imsqti_assessment_xmlv2p1") != -1) {
eval.setValid(true);
break;
}
}
} else {
eval.setValid(false);
}
} catch(NoSuchFileException nsfe) {
eval.setValid(false);
} catch (IOException | IllegalArgumentException e) {
log.error("", e);
eval.setValid(false);
} finally {
IOUtils.closeQuietly(reader);
}
return eval;
}
private static class ImsManifestFileFilter extends SimpleFileVisitor<Path> {
private boolean imsManifestFile;
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
String filename = file.getFileName().toString();
if("imsmanifest.xml".equals(filename)) {
imsManifestFile = true;
}
return imsManifestFile ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
}
public boolean isValid() {
return imsManifestFile;
}
}
public static boolean isOnyxTest(File zipfile) {
// <OLTACE-72>
File unzippedDir = null;
if (zipfile.getName().toLowerCase().endsWith(".zip")) {
unzippedDir = new File(zipfile.getAbsolutePath().substring(0, zipfile.getAbsolutePath().length() - 4) + "__unzipped");
if (!unzippedDir.exists()) {
unzippedDir.mkdir();
}
ZipUtil.unzip(zipfile, unzippedDir);
zipfile = unzippedDir;
}
// </OLTACE-72>
BufferedReader br = null;
try {
File mani = new File(zipfile.getAbsolutePath() + "/imsmanifest.xml");
br = new BufferedReader(new FileReader(mani));
while (br.ready()) {
String l = br.readLine();
if (l.indexOf("imsqti_xmlv2p1") != -1 || l.indexOf("imsqti_test_xmlv2p1") != -1 || l.indexOf("imsqti_assessment_xmlv2p1") != -1) {
br.close();
// <OLTACE-72>
if (unzippedDir != null) {
unzippedDir.delete();
}
// </OLTACE-72>
return true;
}
}
br.close();
} catch (Exception e) {
IOUtils.closeQuietly(br);
}
// <OLTACE-72>
if (unzippedDir != null) {
unzippedDir.delete();
}
// </OLTACE-72>
return false;
}
// <OLATCE-713>
public String getOnyxUserViewLocation() {
return onyxUserViewLocation;
}
public void setOnyxUserViewLocation(String onyxUserViewLocation) {
this.onyxUserViewLocation = onyxUserViewLocation;
}
public String getOnyxReporterUserViewLocation() {
return onyxReporterUserViewLocation;
}
public void setOnyxReporterUserViewLocation(String onyxReporterUserViewLocation) {
this.onyxReporterUserViewLocation = onyxReporterUserViewLocation;
}
// </OLATCE-713>
/**
* This method looks for the latest qtiResultSet in the DB which belongs to
* this course node and user and updates the UserScoreEvaluation.
*/
public static ScoreEvaluation getUserScoreEvaluationFromQtiResult(Long courseResourceableId, QTICourseNode courseNode, boolean bestResultConfig,
Identity identity) {
QTIResultManager qrm = QTIResultManager.getInstance();
List<QTIResultSet> resultSets = qrm.getResultSets(courseResourceableId, courseNode.getIdent(), courseNode.getReferencedRepositoryEntry().getKey(),
identity);
QTIResultSet currentResultSet = null;
for (QTIResultSet resultSet : resultSets) {
if (resultSet.getSuspended()) {
continue;
}
if (currentResultSet == null) {
currentResultSet = resultSet;
continue;
}
// if a best score is given select the latest resultset with the best score
if (bestResultConfig) {
if (resultSet.getScore() > currentResultSet.getScore()) {
currentResultSet = resultSet;
} else if ((resultSet.getScore() == currentResultSet.getScore()) && (resultSet.getCreationDate().after(currentResultSet.getCreationDate()))) {
currentResultSet = resultSet;
}
} else if (resultSet.getCreationDate().after(currentResultSet.getCreationDate())) {
currentResultSet = resultSet;
}
}
if (currentResultSet != null && !currentResultSet.getSuspended()) {
// <OLATCE-374>
AssessmentEntryStatus status = null;
if(currentResultSet.getFullyAssessed() != null && currentResultSet.getFullyAssessed().booleanValue()) {
status = AssessmentEntryStatus.done;
}
return new ScoreEvaluation(currentResultSet.getScore(), currentResultSet.getIsPassed(),
status, Boolean.TRUE, currentResultSet.getFullyAssessed(), currentResultSet.getAssessmentID());
// </OLATCE-374>
}
return null;
}
public static boolean existsResultSet(Long courseResourceableId, QTICourseNode courseNode, Identity identity, Long assessmentId) {
QTIResultManager qrm = QTIResultManager.getInstance();
List<QTIResultSet> resultSets = qrm.getResultSets(courseResourceableId, courseNode.getIdent(), courseNode.getReferencedRepositoryEntry().getKey(),
identity);
for (QTIResultSet resultSet : resultSets) {
if(Long.valueOf(resultSet.getAssessmentID()).equals(assessmentId) && !resultSet.getSuspended()) {
return true;
}
}
return false;
}
}