Package org.olat.modules.scorm.server.servermodels

Source Code of org.olat.modules.scorm.server.servermodels.ScoDocument

package org.olat.modules.scorm.server.servermodels;

/**
* RELOAD TOOLS Copyright (c) 2003 Oleg Liber, Bill Olivier, Phillip Beauvoir,
* Paul Sharples Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom
* the Software is furnished to do so, subject to the following conditions: The
* above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS
* IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Project
* Management Contact: Oleg Liber Bolton Institute of Higher Education Deane
* Road Bolton BL3 5AB UK e-mail: o.liber@bolton.ac.uk Technical Contact:
* Phillip Beauvoir e-mail: p.beauvoir@bolton.ac.uk Paul Sharples e-mail:
* p.sharples@bolton.ac.uk Web: http://www.reload.ac.uk
*/

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.jdom.Document;
import org.jdom.Element;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.modules.scorm.ISettingsHandler;

import uk.ac.reload.jdom.XMLDocument;

/**
* ScoDocument. A class that allows us to read in a CMI datamodel for a
* particular item, get at the datamodel and allow the javascript runtime model
* to access the elements, read in the values to its model and also update back
* to the server model with any changes once the user has "browsed" it.
*
* @author Paul Sharples
*/
public class ScoDocument extends XMLDocument {
  // var used to flag if the sco was "failed"
  private boolean isFailed = false;

  // some more vars used to keep the initial settings for a CMI_DataModel
  // so we can re-generate a "clean slate" easily, if a user has failed a sco
  private String _userId;
  private String _userName;
  private String _max_time_allowed;
  private String _time_limit_action;
  private String _data_from_lms;
  private String _mastery_score;
  private String _lesson_location;
  private String _lesson_mode;
  private String _credit_mode;

  /**
   * Some sco data elements that will appear in every sco data model This is
   * split intot part_a and part_b so we can insert 'n' number of <objective>
   * nodes if the sco creates them during a session
   */
  public static String[] _cmivalues_a = { "cmi.core.student_id", "cmi.core.student_name", "cmi.core.lesson_location", "cmi.core.credit",
      "cmi.core.lesson_status", "cmi.core.entry", "cmi.core.score.raw", "cmi.core.score.max", "cmi.core.score.min", "cmi.core.total_time",
      "cmi.core.lesson_mode", "cmi.core.exit", "cmi.core.session_time", "cmi.suspend_data", "cmi.launch_data", "cmi.comments",
      "cmi.comments_from_lms", };

  /**
   * <objective> nodes go between part_a and part_b
   */
  public static String[] _cmivalues_b = { "cmi.student_data.mastery_score", "cmi.student_data.max_time_allowed",
      "cmi.student_data.time_limit_action", "cmi.student_preference.audio", "cmi.student_preference.language",
      "cmi.student_preference.speed", "cmi.student_preference.text", };

  // <interactions> nodes go after part_b

  // We will need to read the xml doc model and find out if it contains
  // items that were previously created by the sco, such as
  // objectives/interactions
  // Once we know this we can dynamically add those element together with the
  // the values that appear in every cmi model to create a complete list.
  // this array will contain ALL cmi element that appear in this sco model
  protected String[] _cmiValuesForThisDoc;

  // some static variables
  protected static final String OBJECTIVES_COUNT = "cmi.objectives._count";
  protected static final String INTERACTIONS_COUNT = "cmi.interactions._count";

  protected String _totalTimeHolder;
  private ISettingsHandler settings;

  /**
   * Default constuctor
   */
  public ScoDocument(ISettingsHandler settings) {
    this.settings = settings;
  }

  /**
   * A method to load into JDOM, a particular sco model given the item
   * identifier.
   *
   * @param scoID - the Item identifier for a sco
   */
  public void loadDocument(String scoID) {
    /*
     * we need to figure out the file reference from the scoId. When a sco was
     * originally imported an xmlfile representing the item was created, so we
     * need to load that document in. If it isn't there for some reason, then
     * we've got a problem.
     */
    File pathToNavFile = settings.getScoDataModelFile(scoID);
    // Make sure its there
    if (pathToNavFile.exists()) {
      try {
        super.loadDocument(pathToNavFile);

        Element root = getDocument().getRootElement();

        // set these now, so they are easy to get to when we need them later.
        _userId = getElement(root, "cmi.core.student_id").getText();
        _userName = getElement(root, "cmi.core.student_name").getText();
        _lesson_location = getElement(root, "cmi.core.lesson_location").getText();
        _max_time_allowed = getElement(root, "cmi.student_data.max_time_allowed").getText();
        _time_limit_action = getElement(root, "cmi.student_data.time_limit_action").getText();
        _mastery_score = getElement(root, "cmi.student_data.mastery_score").getText();
        _data_from_lms = getElement(root, "cmi.launch_data").getText();
        _lesson_mode = getElement(root, "cmi.core.lesson_mode").getText();
        _credit_mode = getElement(root, "cmi.core.credit").getText();
        // ** If a user failed a sco, then they get a new clean slate of data
        // so here we will flag this...
        if (getElement(root, "cmi.core.lesson_status").getText().equals("failed")) {
          // remember the total time.
          _totalTimeHolder = getElement(root, "cmi.core.total_time").getText();
          isFailed = true;
        } else {
          isFailed = false;
        }
      } catch (Exception ex) {
        throw new OLATRuntimeException(this.getClass(), "error: could not load sco model for " + scoID, ex);
      }
    } else {
      throw new OLATRuntimeException(this.getClass(), "error: could not find sco model for " + scoID, null);
    }
  }

  /**
   * getScoModel() - This method searches the CMI xml file that was created when
   * a scorm package was imported. It first gets all part_a elements that appear
   * in every sco file. It then looks for any <objective> elements, then looks
   * for part_b elements. Finally it then looks for any <interactions>. All of
   * these values are added to a vector (in the correct order). The vector is
   * then copied into a 2_D array, the first index representing an element/value
   * combination. The second index of this 2-D array allows us to get at our
   * name/value combinations (i.e. the second index only contains 2 elements 0 -
   * being the element name, 1 being its value)
   *
   * @return - a 2-D array of name value pairs.
   */
  public String[][] getScoModel() {
    // if a sco was previously failed, then generate a clean model first...
    if (isFailed) {
      try {
        setDocument(formatCleanScoModel());
        // need to set the total time here
        saveDocument();
      } catch (IOException ex) {
        throw new OLATRuntimeException(this.getClass(), "Error: could not reset sco to its original state", ex);
      }
    }

    Vector allElements = new Vector();
    // first add all part A cmi model components...
    for (int i = 0; i < _cmivalues_a.length; i++) {
      String[] _cmicomponents = new String[2];
      _cmicomponents[0] = _cmivalues_a[i];
      _cmicomponents[1] = getElement(getDocument().getRootElement(), _cmivalues_a[i]).getText();
      allElements.add(_cmicomponents);
    }

    // next find if there are any objectives in the model
    Element objectivesCount = getElement(getDocument().getRootElement(), OBJECTIVES_COUNT);
    if (objectivesCount != null) {
      int noOfObjectives = Integer.parseInt(objectivesCount.getText());
      String[] _cmiobjectivescount = new String[2];
      _cmiobjectivescount[0] = OBJECTIVES_COUNT;
      _cmiobjectivescount[1] = objectivesCount.getText();
      allElements.add(_cmiobjectivescount);
      if (noOfObjectives > 0) {
        allElements.addAll(getObjectivesNodesFromModel(noOfObjectives));
      }
    }

    // next add all part B cmi model components...
    for (int i = 0; i < _cmivalues_b.length; i++) {
      String[] _cmicomponents = new String[2];
      _cmicomponents[0] = _cmivalues_b[i];
      _cmicomponents[1] = getElement(getDocument().getRootElement(), _cmivalues_b[i]).getText();
      allElements.add(_cmicomponents);
    }

    // next find if there are any interaction in the model
    Element interactionsCount = getElement(getDocument().getRootElement(), INTERACTIONS_COUNT);
    if (interactionsCount != null) {
      int noOfInteractions = Integer.parseInt(interactionsCount.getText());
      String[] _cmiinteractionscount = new String[2];
      _cmiinteractionscount[0] = INTERACTIONS_COUNT;
      _cmiinteractionscount[1] = interactionsCount.getText();
      allElements.add(_cmiinteractionscount);
      if (noOfInteractions > 0) {
        allElements.addAll(getInteractionsNodesFromModel());
      }
    }

    // finally copy the vector into our _cmiValuesForThisDoc string array
    // above so a complete list for this sco is available
    String[][] _cmiValuesForThisDoc = new String[allElements.size()][2];
    allElements.copyInto(_cmiValuesForThisDoc);
    // check for updates that may be needed in datamodel
    return doFinalPreUpdate(_cmiValuesForThisDoc, true);
  }

  /**
   * Method to get all interaction & children nodes from our JDOM datamodel doc
   *
   * @return a vector containing a 2-d array of name/values for interactions
   */
  private Vector getInteractionsNodesFromModel() {
    Vector interactionsElements = new Vector();
    Element id = getElement(getDocument().getRootElement(), "cmi.interactions");
    List interactionList = id.getChildren("interaction");
    Iterator listElement = interactionList.iterator();
    while (listElement.hasNext()) {
      Element anInteraction = (Element) listElement.next();
      String interactionIndex = anInteraction.getAttributeValue("index");

      String[] interactionIDNameValue = new String[2];
      interactionIDNameValue[0] = "cmi.interactions." + interactionIndex + ".id";
      interactionIDNameValue[1] = anInteraction.getChild("id").getText();
      interactionsElements.add(interactionIDNameValue);

      // objectives
      Element interactionObjectives = anInteraction.getChild("objectives");
      if (interactionObjectives != null) {
        List objectiveList = interactionObjectives.getChildren("objective");
        if (objectiveList != null) {
          Iterator objListElement = objectiveList.iterator();
          while (objListElement.hasNext()) {
            Element anObjective = (Element) objListElement.next();
            String objectiveIndex = anObjective.getAttributeValue("index");
            String[] interactionObjectiveID = new String[2];
            interactionObjectiveID[0] = "cmi.interactions." + interactionIndex + ".objectives." + objectiveIndex + ".id";
            interactionObjectiveID[1] = anObjective.getChild("id").getText();
            interactionsElements.add(interactionObjectiveID);
          }
        }
      }

      String[] interactionTimeNameValue = new String[2];
      interactionTimeNameValue[0] = "cmi.interactions." + interactionIndex + ".time";
      interactionTimeNameValue[1] = anInteraction.getChild("time").getText();
      interactionsElements.add(interactionTimeNameValue);

      String[] interactionTypeNameValue = new String[2];
      interactionTypeNameValue[0] = "cmi.interactions." + interactionIndex + ".type";
      interactionTypeNameValue[1] = anInteraction.getChild("type").getText();
      interactionsElements.add(interactionTypeNameValue);

      // correct_responses

      Element interactionCorrectResponses = anInteraction.getChild("correct_responses");
      if (interactionCorrectResponses != null) {
        List correctResponseList = interactionCorrectResponses.getChildren("correct_response");
        if (correctResponseList != null) {
          Iterator correctResponseListElement = correctResponseList.iterator();
          while (correctResponseListElement.hasNext()) {
            Element aCorrectResponse = (Element) correctResponseListElement.next();
            String correctResponseIndex = aCorrectResponse.getAttributeValue("index");
            String[] correctResponse = new String[2];
            correctResponse[0] = "cmi.interactions." + interactionIndex + ".correct_responses." + correctResponseIndex + ".pattern";
            correctResponse[1] = aCorrectResponse.getChild("pattern").getText();
            interactionsElements.add(correctResponse);
          }
        }
      }

      String[] interactionWeightingNameValue = new String[2];
      interactionWeightingNameValue[0] = "cmi.interactions." + interactionIndex + ".weighting";
      interactionWeightingNameValue[1] = anInteraction.getChild("weighting").getText();
      interactionsElements.add(interactionWeightingNameValue);

      String[] interactionStudentResponseNameValue = new String[2];
      interactionStudentResponseNameValue[0] = "cmi.interactions." + interactionIndex + ".student_response";
      interactionStudentResponseNameValue[1] = anInteraction.getChild("student_response").getText();
      interactionsElements.add(interactionStudentResponseNameValue);

      String[] interactionResultNameValue = new String[2];
      interactionResultNameValue[0] = "cmi.interactions." + interactionIndex + ".result";
      interactionResultNameValue[1] = anInteraction.getChild("result").getText();
      interactionsElements.add(interactionResultNameValue);

      String[] interactionLatencyNameValue = new String[2];
      interactionLatencyNameValue[0] = "cmi.interactions." + interactionIndex + ".latency";
      interactionLatencyNameValue[1] = anInteraction.getChild("latency").getText();
      interactionsElements.add(interactionLatencyNameValue);
    }
    return interactionsElements;
  }

  /**
   * getObjectivesNodesFromModel - searches the CMI xml model for a particular
   * sco for <objective> elements, looping around "cmi.objectives.n" times where
   * 'n' is the number of objectives declared in the "cmi.objectives._count"
   * element of the model. These element/values are stored as 2-D arrays and
   * copied to a vector.
   *
   * @param numberOfObjectives
   * @return objectives nodes
   * @returns a vector of obectives found in model
   */
  private Vector getObjectivesNodesFromModel(int numberOfObjectives) {
    Vector objectiveElements = new Vector();
    Element id = getElement(getDocument().getRootElement(), "cmi.objectives");
    List objectiveList = id.getChildren("objective");
    Iterator listElement = objectiveList.iterator();
    while (listElement.hasNext()) {
      Element anObjective = (Element) listElement.next();
      String objectiveIndex = anObjective.getAttributeValue("index");
      Element score = anObjective.getChild("score");

      String[] objectiveIDNameValue = new String[2];
      objectiveIDNameValue[0] = "cmi.objectives." + objectiveIndex + ".id";
      objectiveIDNameValue[1] = anObjective.getChild("id").getText();
      objectiveElements.add(objectiveIDNameValue);

      String[] objectiveScoreRaw = new String[2];
      objectiveScoreRaw[0] = "cmi.objectives." + objectiveIndex + ".score.raw";
      objectiveScoreRaw[1] = score.getChild("raw").getText();
      objectiveElements.add(objectiveScoreRaw);

      String[] objectiveScoreMax = new String[2];
      objectiveScoreMax[0] = "cmi.objectives." + objectiveIndex + ".score.max";
      objectiveScoreMax[1] = score.getChild("max").getText();
      objectiveElements.add(objectiveScoreMax);

      String[] objectiveScoreMin = new String[2];
      objectiveScoreMin[0] = "cmi.objectives." + objectiveIndex + ".score.min";
      objectiveScoreMin[1] = score.getChild("min").getText();
      objectiveElements.add(objectiveScoreMin);

      String[] objectiveStatus = new String[2];
      objectiveStatus[0] = "cmi.objectives." + objectiveIndex + ".status";
      objectiveStatus[1] = anObjective.getChild("status").getText();
      objectiveElements.add(objectiveStatus);
    }
    String[] objectiveCount = new String[2];
    objectiveCount[0] = OBJECTIVES_COUNT;
    objectiveCount[1] = String.valueOf(numberOfObjectives);
    objectiveElements.add(objectiveCount);
    return objectiveElements;
  }

  /**
   * getElement. A utility method which takes a JDOM element (usually root) and
   * a "cmi" property name (i.e. "cmi.core.student_id") to search for as
   * parameter. The method spilts the cmi value into individual parts and
   * searches down the tree until the correct JDOM element is found. If the
   * element does not exist in the model, then null is returned.
   *
   * @param element
   * @param propertyName
   * @return the searched element
   */
  public Element getElement(Element element, String propertyName) {
    Element current = element;
    String[] cmiBits = propertyName.split("\\.");
    for (int i = 1; i < cmiBits.length; i++) {
      Element next = current.getChild(cmiBits[i]);
      if (next != null) {
        current = next;
      } else {
        return null;
      }
    }
    return current;
  }

  /**
   * Method to allow us to make changes to the model (if we need to) before it
   * is saved back to disk & also changes to be made once loaded
   *
   * @param scoElementsPreUpdate - 2D array of all name/value pairs
   * @param loadFlag
   * @return 2D Array of all name/value pairs
   */
  public String[][] doFinalPreUpdate(String[][] scoElementsPreUpdate, boolean loadFlag) {
    // Keying the elements into a map will make this easier
    Map<String,String> keyedElements = new HashMap<String,String>();
    for (int i = 0; i < scoElementsPreUpdate.length; i++) {
      keyedElements.put(scoElementsPreUpdate[i][0], scoElementsPreUpdate[i][1]);
    }
    // ***********************************************************************
    // *** bug fix 2003-11-12
    // We need to check to see if theres anything in cmi.core.session_time
    // before
    // we call "addTimes"
    // first we'll update the total_time
    if (loadFlag == false) {
      if (!keyedElements.get("cmi.core.session_time").equals("")) {
        String totalTime = ScoUtils.addTimes((String) keyedElements.get("cmi.core.total_time"), (String) keyedElements
            .get("cmi.core.session_time"));
        keyedElements.put("cmi.core.total_time", totalTime);
      }
    }
    if (loadFlag == true) {
      String exitStatus = (String) keyedElements.get("cmi.core.exit");
      // now update the entry for next time...
      String entryStatus = "";
      /*
       * Bugfix 2005-08-'8 Guido only "suspend" sets entryStatus to "resume"
       *  "logout" sets it to "". Not as written in the spec but the testsuite depends on it.
       */
      if (exitStatus.equals("suspend") || exitStatus.equals("logout")) {
        if (exitStatus.equals("suspend")) entryStatus = "resume";
        if (exitStatus.equals("logout")) entryStatus = "";
        exitStatus = "";
        keyedElements.put("cmi.core.entry", entryStatus);
        keyedElements.put("cmi.core.exit", exitStatus);
      }
    }
    // **********************************************************************
    // create a 2-d array to pass back
    String[][] finalArray = new String[keyedElements.size()][2];
    // finally copy all the elements back into the 2d array and return..
    int i = 0;
    for (Map.Entry<String, String> entry:keyedElements.entrySet()) {
      finalArray[i][0] = entry.getKey();
      finalArray[i][1] = entry.getValue();
      i++;
    }
    keyedElements.clear();
    return finalArray;
  }

  /**
   * doLmsCommit. This method takes the Javascript cmi model and attempts to
   * update the CMI xml sco file. It takes the javascript model as a 2-D array
   * of name/value pairs, finds the element in the JDOM model, updates it with
   * the new value and then writes the new model back to disk.
   *
   * @param scoElementsPreUpdate
   */
  public void doLmsCommit(String[][] scoElementsPreUpdate) {
    // System.out.println("reloadScoDocuement - dolmscommit is called");
    String[][] scoElements = doFinalPreUpdate(scoElementsPreUpdate, false);
    Vector objectives = new Vector();
    Vector interactions = new Vector();
    int objectivesCount = 0;
    int interactionsCount = 0;

    for (int i = 0; i < scoElements.length; i++) {
      if (scoElements[i][0].startsWith("cmi.objectives")) {
        if (scoElements[i][0].equals("cmi.objectives._count")) {
          objectivesCount = Integer.parseInt(scoElements[i][1]);
        } else {
          objectives.add(scoElements[i]);
        }
      } else if (scoElements[i][0].startsWith("cmi.interactions")) {
        if (scoElements[i][0].equals("cmi.interactions._count")) {
          interactionsCount = Integer.parseInt(scoElements[i][1]);
        } else {
          interactions.add(scoElements[i]);
        }
      } else {
        Element itemToChange = getElement(getDocument().getRootElement(), scoElements[i][0]);
        if (itemToChange != null) {
          itemToChange.setText(scoElements[i][1]);
        }
      }
    }
    // update the objectives
    String[][] objectivesArray = new String[objectives.size()][2];
    objectives.copyInto(objectivesArray);
    if (objectivesCount > 0) dealWithSavingObjectives(objectivesCount, objectivesArray);
    objectives.clear();
    // update the interactions
    String[][] interactionsArray = new String[interactions.size()][2];
    interactions.copyInto(interactionsArray);
    if (interactionsCount > 0) dealWithSavingInteractions(interactionsCount, interactionsArray);
    interactions.clear();
    // finally commit this back to disk
    try {
      saveDocument();
    } catch (IOException ex) {
      throw new OLATRuntimeException(this.getClass(), "Error: could not save sco model:", ex);
    }
  }

  /**
   * Method to clear a sco back to its original state - this would happen if the
   * sco set lesson_status to "failed"
   *
   * @return a new JDOM doc
   */
  public Document formatCleanScoModel() {
    CMI_DataModel cleanData = new CMI_DataModel(_userId, _userName, _max_time_allowed, _time_limit_action, _data_from_lms, _mastery_score,
        _lesson_mode, _credit_mode);
    cleanData.buildFreshModel();
    Document theModel = cleanData.getModel();
    cleanData.setDocument(theModel);
    return cleanData.getDocument();
  }

  /**
   * Method to update our cmi datamodel with interaction elements that were
   * created at runtime by a sco. Before the sco is taken, the xml model will
   * not contain any <interaction> nodes. So the important job is to look at the
   * values provided by the javascript model and ascertain if we need to create
   * any new JDOM nodes. Once any new nodes are created, we can then (using a
   * hashtable) use keys to update each node in the document. If the node did
   * not exist in the xml doc, then there would be problems, possibly lost
   * information.
   *
   * @param count - the number of interactions (obtained from _count)
   * @param interactions - the 2d array of name/value pairs
   */
  public void dealWithSavingInteractions(int count, String[][] interactions) {
    // first find out how many interactions there are already
    int interactionsCount = Integer.parseInt(getElement(getDocument().getRootElement(), "cmi.interactions._count").getText());

    // Keying the elements into a hashtable will make this easier
    Hashtable keyedInteractions = new Hashtable();
    for (int i = 0; i < interactions.length; i++) {
      keyedInteractions.put(interactions[i][0], interactions[i][1]);
    }

    // if there are more elements in the javascript model, then we need
    // to create the same number of <interaction> nodes in our JDOM document...
    if (count > interactionsCount) {
      for (int i = interactionsCount; i < count; i++) {

        int noOfObjectives = Integer.parseInt((String) keyedInteractions.get("cmi.interactions." + i + ".objectives._count"));
        String interaction = (String) keyedInteractions.get("cmi.interactions." + i + ".correct_responses._count");
        // added fix because reload code depends on the
        // xx.correct_responses._count if objectives._count is set.
        int noOfCorrectResponses = 0;
        if (interaction != null) {
          noOfCorrectResponses = Integer.parseInt(interaction);
        }
        createNewInteraction(i, noOfObjectives, noOfCorrectResponses);
      }
    }
    // ****** need to make sure existing ones have right amount of objectives &
    // correct_responses

    // now that the correct number of JDOM nodes exist in the model, we
    // can start to update each node with the data contained in our hashtable
    Element interactionElement = getElement(getDocument().getRootElement(), "cmi.interactions");
    List interactionList = interactionElement.getChildren("interaction");
    Iterator listElement = interactionList.iterator();
    while (listElement.hasNext()) {
      Element anInteraction = (Element) listElement.next();
      String interactionIndex = anInteraction.getAttributeValue("index");
      // set the ID for this interaction
      anInteraction.getChild("id").setText((String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".id"));
      // next we need to update 'n' number of of <cmi.interactions.n.objective>
      // nodes
      Element objectives = anInteraction.getChild("objectives");
      // get the list of <objective> nodes
      List objList = objectives.getChildren("objective");
      Iterator objListElement = objList.iterator();
      // now go thru all of the child <objective> nodes and update them
      while (objListElement.hasNext()) {
        Element anObjective = (Element) objListElement.next();
        // find the correct index for this node
        String objectiveIndex = anObjective.getAttributeValue("index");
        // now use that index to get the correct key from the hashtable
        // and update the correct JDOM node in our model
        anObjective.getChild("id").setText(
            (String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".objectives." + objectiveIndex + ".id"));
      }
      // update the time
      anInteraction.getChild("time").setText((String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".time"));
      // update the type
      anInteraction.getChild("type").setText((String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".type"));

      // correct_responses
      Element correct_responses = anInteraction.getChild("correct_responses");
      List crList = correct_responses.getChildren("correct_response");
      Iterator crListElement = crList.iterator();
      // now go thru all of the child <correct_response> nodes and update them
      while (crListElement.hasNext()) {
        Element aCorrectResponse = (Element) crListElement.next();
        String crIndex = aCorrectResponse.getAttributeValue("index");
        aCorrectResponse.getChild("pattern").setText(
            (String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".correct_responses." + crIndex + ".pattern"));
      }

      anInteraction.getChild("weighting").setText((String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".weighting"));
      anInteraction.getChild("student_response").setText(
          (String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".student_response"));
      anInteraction.getChild("result").setText((String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".result"));
      anInteraction.getChild("latency").setText((String) keyedInteractions.get("cmi.interactions." + interactionIndex + ".latency"));
    }
    // once finished, update the <_count> to reflect the changes
    getElement(getDocument().getRootElement(), "cmi.interactions._count").setText(count + "");
  }

  /**
   * Method to update our cmi datamodel with objective elements that were
   * created at runtime by a sco. Before the sco is taken, the xml model will
   * not contain any <objective> nodes. So the important job is to look at the
   * values provided by the javascript model and ascertain if we need to create
   * any new JDOM nodes. Once any new nodes are created, we can then (using a
   * hashtable) use keys to update each node in the document. If the node did
   * not exist in the xml doc, then there would be problems, possibly lost
   * information.
   *
   * @param count - the number of objectives found in <_count>
   * @param objectives - the name/value pairs of objective info
   */
  public void dealWithSavingObjectives(int count, String[][] objectives) {
    // first find out how many objectives there are already
    int objectivesCount = Integer.parseInt(getElement(getDocument().getRootElement(), "cmi.objectives._count").getText());

    // if there are more elements in the javascript model, then we need
    // to create the same number of <objective> nodes in our JDOM document...
    if (count > objectivesCount) {
      for (int i = objectivesCount; i < count; i++) {
        createNewObjective(i);
      }
    }
    // next update the elements...
    // Keying the elements into a hashtable will make this easier
    Hashtable keyedObjectives = new Hashtable();
    for (int i = 0; i < objectives.length; i++) {
      keyedObjectives.put(objectives[i][0], objectives[i][1]);
    }
    Element id = getElement(getDocument().getRootElement(), "cmi.objectives");
    List objectiveList = id.getChildren("objective");
    Iterator listElement = objectiveList.iterator();
    while (listElement.hasNext()) {
      Element anObjective = (Element) listElement.next();
      String objectiveIndex = anObjective.getAttributeValue("index");
      // set the ID for this objective
      anObjective.getChild("id").setText((String) keyedObjectives.get("cmi.objectives." + objectiveIndex + ".id"));
      // now move onto the score
      Element score = anObjective.getChild("score");
      // next update the children of <score>
      score.getChild("raw").setText((String) keyedObjectives.get("cmi.objectives." + objectiveIndex + ".score.raw"));
      score.getChild("max").setText((String) keyedObjectives.get("cmi.objectives." + objectiveIndex + ".score.max"));
      score.getChild("min").setText((String) keyedObjectives.get("cmi.objectives." + objectiveIndex + ".score.min"));
      // finally update <status>
      anObjective.getChild("status").setText((String) keyedObjectives.get("cmi.objectives." + objectiveIndex + ".status"));
    }
    // once finished, update the <_count> to reflect the changes
    getElement(getDocument().getRootElement(), "cmi.objectives._count").setText(count + "");
  }

  /**
   * createNewInteraction() - Creates a new <interaction> element structure in
   * the xml CMI datamodel for a particular sco.
   *
   * @param index - the index for the <interaction> to be created
   * @param noOfObjectives - the number of objective nodes to create
   * @param noOfCorrectResponses - the number of correct_responses nodes to
   *          create
   */
  private void createNewInteraction(int index, int noOfObjectives, int noOfCorrectResponses) {
    // first get hold of the <interactions> node
    Element ints = getElement(getDocument().getRootElement(), "cmi.interactions");
    // and then add our <interaction index=""> node
    Element interaction = new Element("interaction");
    interaction.setAttribute("index", index + "");
    ints.addContent(interaction);
    // add <id>
    Element id = new Element("id");
    interaction.addContent(id);

    // add <objectives>
    Element objectives = new Element("objectives");
    interaction.addContent(objectives);
    // add <_count> to <objectives>
    Element _count = new Element("_count");
    _count.setText(noOfObjectives + "");
    objectives.addContent(_count);

    // add all the objectives needed
    for (int i = 0; i < noOfObjectives; i++) {
      Element objectiveItem = new Element("objective");
      objectiveItem.setAttribute("index", i + "");
      Element objID = new Element("id");
      objectiveItem.addContent(objID);
      // now add this lot to the <objectives> node
      objectives.addContent(objectiveItem);
    }

    // add <time>
    Element time = new Element("time");
    interaction.addContent(time);
    // add <type>
    Element type = new Element("type");
    interaction.addContent(type);

    // add <correct_responses>
    Element correct_responses = new Element("correct_responses");
    interaction.addContent(correct_responses);
    // add <_count> to <correct_responses>
    Element _countCR = new Element("_count");
    _countCR.setText(noOfCorrectResponses + "");
    correct_responses.addContent(_countCR);

    // add all the correct_responses needed
    for (int i = 0; i < noOfCorrectResponses; i++) {
      Element correctResponseItem = new Element("correct_response");
      correctResponseItem.setAttribute("index", i + "");
      Element pattern = new Element("pattern");
      correctResponseItem.addContent(pattern);
      // now add this lot to the <correct_responses> node
      correct_responses.addContent(correctResponseItem);
    }

    // add <weighting>
    Element weighting = new Element("weighting");
    interaction.addContent(weighting);
    // add <student_response>
    Element student_response = new Element("student_response");
    interaction.addContent(student_response);
    // add <result>
    Element result = new Element("result");
    interaction.addContent(result);
    // add <latency>
    Element latency = new Element("latency");
    interaction.addContent(latency);
  }

  /**
   * createNewObjective() - Creates a new <objective> element structure in the
   * xml CMI datamodel for a particular sco.
   *
   * @param index - the index for the <objective> to be created
   */
  private void createNewObjective(int index) {
    // first get hold of the <objectives> node
    Element objs = getElement(getDocument().getRootElement(), "cmi.objectives");
    // and then add our <objective index=""> node
    Element objective = new Element("objective");
    objective.setAttribute("index", index + "");
    objs.addContent(objective);
    // add <id>
    Element id = new Element("id");
    objective.addContent(id);
    // add <score>
    Element score = new Element("score");
    objective.addContent(score);
    // add <_children>
    Element _children = new Element("_children");
    _children.setText("raw,max,min");
    score.addContent(_children);
    // add <raw>
    Element raw = new Element("raw");
    score.addContent(raw);
    // add <max>
    Element max = new Element("max");
    score.addContent(max);
    // add <min>
    Element min = new Element("min");
    score.addContent(min);
    // add <status>
    Element status = new Element("status");
    objective.addContent(status);
  }
}
TOP

Related Classes of org.olat.modules.scorm.server.servermodels.ScoDocument

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.