de.bps.onyx.util.ExamPool.java Source code

Java tutorial

Introduction

Here is the source code for de.bps.onyx.util.ExamPool.java

Source

/**
 * <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.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.io.IOUtils;
import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.UserConstants;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.WebappHelper;
import org.olat.course.CourseFactory;
import org.olat.course.ICourse;
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.IQSURVCourseNode;
import org.olat.course.nodes.iq.IQEditController;
import org.olat.fileresource.FileResourceManager;
import org.olat.ims.qti.QTIResultSet;
import org.olat.repository.RepositoryEntry;

import de.bps.onyx.plugin.OnyxModule;
import de.bps.onyx.plugin.OnyxResultManager;
import de.bps.onyx.plugin.wsclient.OnyxExamMode;
import de.bps.onyx.plugin.wsclient.OnyxExamModeService;
import de.bps.onyx.plugin.wsserver.MapWrapper;
import de.bps.onyx.plugin.wsserver.StudentIdsWrapper;
import de.bps.onyx.plugin.wsserver.TestState;
import de.bps.onyx.plugin.wsserver.TraineeStatusService;
import de.bps.webservices.clients.onyxreporter.OnyxReporterConnectorFileNameFilter;

public class ExamPool implements Serializable {
    private static final long serialVersionUID = 4771377056688023915L;

    private static final String PARAM_FIRSTNAME = "firstname";
    private static final String PARAM_LASTNAME = "lastname";
    private static final String PARAM_LANGUAGE = "language";
    private static final String PARAM_STATUS = "status";

    private static final String IS_SURVEY = "isSurvey";
    private static final String IS_SYNCHRONIZED = "isSynchronized";
    private static final String SHOW_SOLUTION = "showSolution";
    public static final String CONTINUATION_ALLOWED = "continuationAllowed";
    public static final String SUSPENSION_ALLOWED = "suspendAllowed";
    public static final String TEMPLATE_ID = "templateid";

    private transient final static OLog log = Tracing.createLoggerFor(ExamPool.class);

    //mapping for key : assessmentId value: resultSet
    private final Map<Long, Identity> assessmentIdentityMapping = new ConcurrentHashMap<Long, Identity>();
    //mapping for key: identityId value:assessmentId
    private final Map<Long, Long> identityAssessmentMapping = new ConcurrentHashMap<Long, Long>();
    //mapping for key: identity value: state
    private final Map<Identity, TestState> studentStates = new ConcurrentHashMap<Identity, TestState>();

    private final Long testSessionId;

    private final static String providerId = OnyxModule.getConfigName();

    //   private final Boolean sessionInitializied;

    private transient final OnyxExamModeService service;
    private transient ICourse referencedCourse;
    private transient CourseNode referencedCourseNode;

    //private String nodeIdent;
    private Long courseId;

    private final File directory = new File(WebappHelper.getUserDataRoot());

    ExamPool(Long testSessionId) {
        super();
        //this.course = course;
        this.testSessionId = testSessionId;
        OnyxExamMode examMode = new OnyxExamMode();
        service = examMode.getOnyxExamModeServicesPort();
    }

    /**
     * This method registers a new exam at the onyx-exam-service, it should be
     * only called by the {@link ExamPoolManager} and not the proxies
     * 
     * @param course
     * @param courseNode
     */
    void initExamPool(ICourse course, CourseNode courseNode) {
        RepositoryEntry entry = courseNode.getReferencedRepositoryEntry();
        this.referencedCourse = course;
        this.referencedCourseNode = courseNode;

        //nodeIdent = courseNode.getIdent();
        courseId = course.getResourceableId();

        if (entry != null) {
            byte[] contentPackage = getContentPackage(entry);
            HashMap<String, String> parameterMap = new HashMap<String, String>();
            // set allowShowSolution either to the configured value (!= null) or to defaultvalue false if test or survey, if selftest then the default is true 
            Boolean allowShowSolution = (Boolean) courseNode.getModuleConfiguration()
                    .get(IQEditController.CONFIG_KEY_ALLOW_SHOW_SOLUTION);
            allowShowSolution = allowShowSolution != null ? allowShowSolution : false;
            parameterMap.put(SHOW_SOLUTION, String.valueOf(allowShowSolution));
            //         Boolean showFeedback = (Boolean) courseNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_ALLOW_SHOW_FEEDBACK);
            //         showFeedback = showFeedback != null ? showFeedback : false;
            //         parameterMap.put(SHOW_FEEDBACK, String.valueOf(showFeedback));
            parameterMap.put(IS_SURVEY, String.valueOf(referencedCourseNode instanceof IQSURVCourseNode));
            Boolean examControl = (Boolean) courseNode.getModuleConfiguration()
                    .get(ExamPoolManager.CONFIG_KEY_EXAM_CONTROL);
            examControl = examControl != null ? examControl : Boolean.FALSE;
            //couple the synchronizedStart again to the examMode
            Boolean synchronizedStart = (Boolean) courseNode.getModuleConfiguration()
                    .get(ExamPoolManager.CONFIG_KEY_EXAM_CONTROL_SYNCHRONIZED_START);
            synchronizedStart = synchronizedStart != null ? synchronizedStart && examControl : false;
            parameterMap.put(IS_SYNCHRONIZED, String.valueOf(synchronizedStart));
            parameterMap.put(CONTINUATION_ALLOWED, Boolean.toString(!examControl.booleanValue()));
            Boolean allowSuspension = (Boolean) courseNode.getModuleConfiguration()
                    .get(IQEditController.CONFIG_KEY_ALLOW_SUSPENSION_ALLOWED);
            allowSuspension = allowSuspension != null ? allowSuspension : false;
            parameterMap.put(SUSPENSION_ALLOWED, String.valueOf(allowSuspension));

            String templateId = courseNode.getModuleConfiguration()
                    .getStringValue(IQEditController.CONFIG_KEY_TEMPLATE);
            if (templateId != null) {
                parameterMap.put(TEMPLATE_ID, templateId);
            }

            MapWrapper wrapper = new MapWrapper();
            wrapper.setMap(parameterMap);
            Long result = service.registerTest(testSessionId, providerId, contentPackage, wrapper);
            log.info("Init result : " + TestState.getState(result) + " for " + testSessionId + " , provider "
                    + providerId + " and parameters " + parameterMap);
        } else {
            log.warn("unable to register new test, no test in course-node");
        }

        refreshGroups();
    }

    private void refreshGroups() {
        if (referencedCourse == null) {
            referencedCourse = CourseFactory.loadCourse(courseId);
        }
        CourseGroupManager man = referencedCourse.getCourseEnvironment().getCourseGroupManager();
        List<Identity> participants = man.getParticipantsFromBusinessGroups();
        for (Identity participant : participants) {
            if (!studentStates.containsKey(participant)) {
                addStudent(participant, null);
            }
        }
    }

    void addStudent(Identity student, TestState state) {
        studentStates.put(student, state != null ? state : TestState.NOT_ENTERED);
    }

    Long registerStudentTest(Identity student, QTIResultSet resultSet, TestState state) {
        Long result = null;

        addStudent(student, (state != null ? state : TestState.WAITING));

        //map this assessment to it's id
        assessmentIdentityMapping.put(resultSet.getAssessmentID(), student);
        //map this student to his / her assessment
        identityAssessmentMapping.put(student.getKey(), resultSet.getAssessmentID());

        HashMap<String, String> parameterMap = new HashMap<String, String>();
        parameterMap.put(PARAM_LANGUAGE, student.getUser().getPreferences().getLanguage());
        parameterMap.put(PARAM_FIRSTNAME, student.getUser().getProperty(UserConstants.FIRSTNAME, null));
        parameterMap.put(PARAM_LASTNAME, student.getUser().getProperty(UserConstants.LASTNAME, null));

        byte[] recommitedFiles = new byte[0];
        if (resultSet.getSuspended()) {
            log.info("Try to recreate for student " + student.getName() + " and suspended assessment : "
                    + resultSet.getAssessmentID());
            //addStudent(student, TestState.SUSPENDED);
            parameterMap.put(PARAM_STATUS, String.valueOf(TestState.RESUME_SUSPENDED.getValue()));
            String assessmentType = referencedCourseNode.getModuleConfiguration()
                    .get(IQEditController.CONFIG_KEY_TYPE).toString();
            String path = null;

            Boolean isSurvey = false;
            File xml = null;
            if (referencedCourseNode instanceof IQSURVCourseNode) {
                isSurvey = true;
            }
            if (isSurvey) {
                OlatRootFolderImpl courseRootContainer = referencedCourse.getCourseEnvironment()
                        .getCourseBaseContainer();
                path = courseRootContainer.getBasefile() + File.separator + referencedCourseNode.getIdent()
                        + File.separator;
                xml = new File(path);
            } else {
                path = OnyxResultManager.getResReporting() + File.separator + student.getName() + File.separator
                        + assessmentType + File.separator;
                xml = new File(directory, path);
            }

            if (xml != null && xml.exists()) {
                File commitMe = null;
                File[] allXmls = xml.listFiles(new OnyxReporterConnectorFileNameFilter(
                        referencedCourseNode.getIdent(), String.valueOf(resultSet.getAssessmentID())));
                if (allXmls != null && allXmls.length > 0) {
                    for (File file : allXmls) {
                        if (file.isFile()) {
                            if (commitMe == null || file.getName().toLowerCase().endsWith(".zip")) {
                                commitMe = file;
                            }
                        }
                    }
                }
                if (commitMe != null) {
                    Long fileLength = commitMe.length();
                    recommitedFiles = new byte[fileLength.intValue()];
                    java.io.FileInputStream inp = null;
                    try {
                        inp = new java.io.FileInputStream(commitMe);
                        inp.read(recommitedFiles);
                        log.info("Found file for suspended assessment for student " + student.getName()
                                + " and suspended assessment : " + resultSet.getAssessmentID() + " # "
                                + commitMe.getAbsolutePath() + "; lenght " + recommitedFiles.length);
                    } catch (FileNotFoundException e) {
                        log.error("Missing file: " + commitMe.getAbsolutePath(), e);
                    } catch (IOException e) {
                        log.error("Error copying file: " + commitMe.getAbsolutePath(), e);
                    } finally {
                        IOUtils.closeQuietly(inp);
                    }
                } else {
                    log.info("Did not find files for suspended assessment for student " + student.getName()
                            + " and suspended assessment : " + resultSet.getAssessmentID() + " at "
                            + xml.getAbsolutePath() + " an filter-options " + referencedCourseNode.getIdent()
                            + " and " + resultSet.getAssessmentID());
                }
            } else {
                log.info("Did not find resreporting folder for student " + student.getName()
                        + " and suspended assessment : " + resultSet.getAssessmentID() + " at "
                        + xml.getAbsolutePath());
            }
        } else {
            log.info("Assessment not suspended, nothing to restore.");
        }

        MapWrapper parameterWrapper = new MapWrapper();
        parameterWrapper.setMap(parameterMap);

        result = service.registerStudent(testSessionId, resultSet.getAssessmentID(), recommitedFiles,
                parameterWrapper);
        log.info("Tried to register student with assessmentId" + resultSet.getAssessmentID() + " to test: "
                + testSessionId + " resulting in : " + TestState.getState(result));
        return result;
    }

    QTIResultSet getAssessmentForStudent(Identity identity) {
        QTIResultSet result = null;
        Long assessmentId = identityAssessmentMapping.get(identity.getKey());
        if (assessmentId != null) {
            result = OnyxResultManager.getResultSet(assessmentId);
        }
        return result;
    }

    /**
     * Use this method to modify the currently hold states of the students, this
     * method should be called by the {@link TraineeStatusService} to keep the
     * shown states in sync with their states in the test
     * 
     * @param identities
     * @param state
     */
    void changeExamState(List<Identity> identities, TestState state) {
        for (Identity identity : identities) {
            changeExamState(identity, state);
        }
    }

    /**
     * Use this method to modify the currently hold state of the student, this
     * method should be called by the {@link TraineeStatusService} to keep the
     * shown states in sync with their states in the test
     * 
     * @param identities
     * @param state
     */
    void changeExamState(Identity identity, TestState state) {
        if (studentStates.containsKey(identity)) {
            studentStates.put(identity, state);
        }
    }

    /**
     * Use this method to send the requested state-changes for the given users
     * to the test. The effects of this call will then be set by the TestPlayer
     * (Onyx) with the help of the {@link TraineeStatusService}
     * 
     * @param identities
     * @param state
     */
    void controllExam(List<Identity> identities, TestState state) {
        StudentIdsWrapper idWrapper = new StudentIdsWrapper();
        ArrayList<Long> assessmentIds = new ArrayList<Long>();
        for (Identity identity : identities) {
            Long assessmentId = identityAssessmentMapping.get(identity.getKey());
            assessmentIds.add(assessmentId);
        }

        idWrapper.setStudentsIds(assessmentIds);

        HashMap<String, String> parameterMap = new HashMap<String, String>();

        parameterMap.put("addTime", TestState.RESUME_ALLOWED == state ? String.valueOf(10) : String.valueOf(0));

        MapWrapper parameterWrapper = new MapWrapper();
        parameterWrapper.setMap(parameterMap);

        Long result = service.testControl(testSessionId, idWrapper, state.getValue(), parameterWrapper);
        log.info("Tried to control exam : " + testSessionId + " for " + assessmentIds + " and state " + state
                + " and parameters " + parameterMap + " resulting in " + TestState.getState(result));
    }

    TestState getStudentState(Identity student) {
        return studentStates.get(student);
    }

    Map<Identity, TestState> getStudentStates() {
        refreshGroups();
        return studentStates;
    }

    Identity getStudentForAssessment(Long assessmentId) {
        Identity student = assessmentId != null ? assessmentIdentityMapping.get(assessmentId) : null;
        return student;
    }

    Long getTestSessionId() {
        return testSessionId;
    }

    private byte[] getContentPackage(RepositoryEntry repositoryEntry) {
        File cpFile = FileResourceManager.getInstance().getFileResource(repositoryEntry.getOlatResource());

        if (cpFile == null || !cpFile.exists()) {
            cpFile = getCP(repositoryEntry);
        }

        Long fileLength = cpFile.length();
        byte[] contentPackage = new byte[fileLength.intValue()];

        java.io.FileInputStream inp = null;
        try {
            inp = new java.io.FileInputStream(cpFile);
            inp.read(contentPackage);
        } catch (FileNotFoundException e) {
            log.error("Missing file: " + cpFile.getAbsolutePath(), e);
        } catch (IOException e) {
            log.error("Error copying file: " + cpFile.getAbsolutePath(), e);
        } finally {
            IOUtils.closeQuietly(inp);
        }

        return contentPackage;
    }

    /**
     * Generates a file object for the given re.
     * 
     * @param repositoryEntry
     * @return
     */
    private File getCP(RepositoryEntry repositoryEntry) {
        //get content-package (= onyx test zip-file)
        OLATResourceable fileResource = repositoryEntry.getOlatResource();
        String unzipedDir = FileResourceManager.getInstance().unzipFileResource(fileResource).getAbsolutePath();
        String zipdirName = FileResourceManager.ZIPDIR;
        String testName = repositoryEntry.getResourcename();
        String pathToFile = unzipedDir.substring(0, unzipedDir.indexOf(zipdirName));
        File onyxTestZip = new File(pathToFile + testName);
        // <OLATCE-499>
        if (!onyxTestZip.exists()) {
            onyxTestZip = new File(pathToFile + "repo.zip");
        }
        // </OLATCE-499>
        return onyxTestZip;
    }

}
/*
history:
    
$Log: ExamPool.java,v $
Revision 1.19  2012-05-30 09:16:14  blaw
OLATCE-2007
* allow resume of suspended surveys
* hidde suspended test-tries in reporter-overview
    
Revision 1.18  2012-05-16 13:30:34  blaw
OLATCE-2007
* improved resume of suspended tests
    
Revision 1.17  2012-05-15 14:11:04  blaw
OLATCE-2021
* catch all exceptions, log them and then throw them back to onyx in returnwsservice
* backup the result-file if errors occurred
* refactored onyx-reporter-requests into a new background-task
* more load/update cycles for qtiresultsets while saving them
    
Revision 1.16  2012-05-09 16:03:48  blaw
OLATCE-2007
* allow suspend and resume of tests
    
Revision 1.15  2012-05-07 13:12:51  laeb
OPEN - issue OLATCE-2009: Unterbrechen: Konfiguration "Unterbrechen erlauben" fr Onyx-Tests freischalten
https://www.bps-system.de/devel/browse/OLATCE-2009
* new test config "suspensionAllowed"
    
Revision 1.14  2012-04-25 13:56:53  blaw
OLATCE-1968
* refresh groups, if new groups or users were added to the course after first registration of the exam
    
Revision 1.13  2012-04-10 15:04:38  laeb
OLATCE-1980 OnyxExamModeService: Um Nutzerdaten erweitern: Vor- und Zuname Student
* added firstname and lastname properties to Onyx registerStudent WS call parameters
    
Revision 1.12  2012-04-10 13:57:48  blaw
OLATCE-1425
* more logging
    
Revision 1.11  2012-04-05 13:49:41  blaw
OLATCE-1425
* added history
* better indention
* refactored referencess for ExamPoolManagers to the abstract class
* added yesNoDialog for StartExam-function
* added more gui-warnings and / or fallback-values if student- or exam-values are not available
    
    
*/