Package org.olat.modules.scorm

Source Code of org.olat.modules.scorm.OLATApiAdapter

/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <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>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.modules.scorm;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.Tracing;
import org.olat.core.util.FileUtils;
import org.olat.core.util.StringHelper;
import org.olat.modules.scorm.manager.ScormManager;
import org.olat.modules.scorm.server.beans.LMSDataFormBean;
import org.olat.modules.scorm.server.beans.LMSDataHandler;
import org.olat.modules.scorm.server.beans.LMSResultsBean;

/**
* OLATApiAdapter implements the ApiAdapter Interface from the pfplms code which was initially
* designed for applet use. For the 'Backend' it uses portions of the code developed for the reload
* scorm player.
* see: http://www.scorm.com/scorm-explained/technical-scorm/run-time/run-time-reference/ for an nice overview of the datamodel
*
* @author guido
*/
public  class OLATApiAdapter implements ch.ethz.pfplms.scorm.api.ApiAdapterInterface {
  private  ch.ethz.pfplms.scorm.api.ApiAdapter core;
  //private ScormTrackingManager scormTracking;
 
  private Hashtable olatScoCmi = new Hashtable();

  private String  olatStudentId;
  private String  olatStudentName;
  //was used as reference id like out repo id
 
  //the sco id
  private String  olatSahsId;

  private boolean isLaunched  = false;
  private boolean isLaunching = false;
 
  private LMSDataHandler odatahandler;
  private ScormManager scormManager;
  private SettingsHandlerImpl scormSettingsHandler;
  private final ScormAPICallback apiCallback;
  //
  private Properties scoresProp; // keys: sahsId; values = raw score of an sco
 
  private final String SCORE_IDENT = "cmi.core.score.raw";
  private File scorePropsFile;
 
  /**
   * creates a new API adapter
   */
  OLATApiAdapter (ScormAPICallback apiCallback) {
    this.apiCallback = apiCallback;
    core = new ch.ethz.pfplms.scorm.api.ApiAdapter ();
  }

  /**
   * @param cpRoot
   * @param repoId
   * @param courseId
   * @param userPath
   * @param studentId - the olat username
   * @param studentName - the students name
   * @param isVerbose prints out what is going on inside the scorm RTE
   */
  public  final void init (File cpRoot, String repoId, String courseId, String storagePath, String studentId, String studentName, String lesson_mode, String credit_mode, int controllerHashCode) {
    this.olatStudentId   = studentId;
    this.olatStudentName = studentName;
    say ("cmi.core.student_id=" +olatStudentId);
    say ("cmi.core.student_name=" +olatStudentName);
    scormSettingsHandler = new SettingsHandlerImpl(cpRoot.getAbsolutePath(), repoId, courseId, storagePath, studentName, studentId, lesson_mode, credit_mode, controllerHashCode);
   
    // get a path for the scores per sco
    String savePath = scormSettingsHandler.getFilePath();
    scorePropsFile = new File(savePath + "/_olat_score.properties");
    scoresProp = new Properties();
    if (scorePropsFile.exists()) {
      InputStream is = null;
      try {
        is = new BufferedInputStream(new FileInputStream(scorePropsFile));
        scoresProp.load(is);
      } catch (IOException e) {
        throw new OLATRuntimeException(this.getClass(), "could not load existing scorm-score-properties file: "+scorePropsFile.getAbsolutePath(),e);
      }
      finally {
        if (is != null) FileUtils.closeSafely(is);
      }
    }
      
   
    scormManager = new ScormManager(cpRoot.getAbsolutePath(), true, true, true, scormSettingsHandler);
  }

  private final void say (String s) {
    if(Tracing.isDebugEnabled(OLATApiAdapter.class)){
      Tracing.logDebug("core: "+s, OLATApiAdapter.class);
    }
  }

  /**
   * @param sahs_id
   */
  public  final void olatLaunchSahs (String sahs_id) {
    /*
    if (System.currentTimeMillis() < clickTime + 1000) {
      say ("Click ignored.");
      return;
    }
    */
    if (isLaunching) {
      say ("SCO " +olatSahsId +" is launching.");
      return;
    }
    if (isLaunched && sahs_id.equals(olatSahsId)) {
      say ("SAHS " +sahs_id +" is already running.");
      return;
    }
    olatScoCmi.clear();

    say ("Launching sahs " +sahs_id);

    if (isLaunched) {
      say ("SAHS "+olatSahsId +" will be unloaded.");
//      IliasLaunchContent (
//         "../sahs_presentation.php?cmd=unloadSahs"
//          +"&sahs_id="  + olatSahsId
//          +"&ref_id="  + olatRefId
//      );
    } else {
      //by launching the sahs the hashtable gets cleard (olatScoCmi)
      //and the php script gererates a page with all the data for this sahs and loads it
      //by calling API.IliasSetValue by javascript
      isLaunching = true;
      //clickTime = System.currentTimeMillis();
      olatSahsId     = sahs_id;
//      IliasLaunchContent (
//         "../sahs_presentation.php?cmd=launchSahs"
//          +"&ref_id="  + olatRefId
//          +"&sahs_id="  + olatSahsId
//      );
      //putting all cmi from the olat storage to the local storage
      LMSDataFormBean lmsDataBean = new LMSDataFormBean();
      lmsDataBean.setItemID(sahs_id);
      lmsDataBean.setLmsAction("get");
      odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
      LMSResultsBean lmsBean = odatahandler.getResultsBean();
      olatScoCmi.clear();
      String[][] strArr = lmsBean.getCmiStrings();
      String key = "";
      String value = "";
      boolean logDebug = Tracing.isDebugEnabled(OLATApiAdapter.class);
      if(strArr != null){
        for(int i=0;i<strArr.length;i++){
            key = strArr[i][0];
            value = strArr[i][1];
            olatScoCmi.put(key, value);
          if(logDebug){
            Tracing.logDebug("passing cmi data to api adapter: "+key +": "+ value, OLATApiAdapter.class);
          }
   
        }
      }
    }
  }

  public  final void olatSetValue (String l, String r) {
    if (r == null) r = ""; // MSIE bug
    say ("OlatSetValue("+l+"="+r+")");
    if (l != null) olatScoCmi.put (l, r);
  }

  public  final void olatAbortSahs (String sahs_id) {
    if (!olatSahsId.equals (sahs_id)) return;
    isLaunching = false;
    if (!isLaunched) return;
    say ("Warning: sahs " +sahs_id +" did not call LMSFinish()");
    olatFinish (false);
    core.reset();
  }

  private  final void olatInitialize () {
    isLaunching = false;
    core.sysPut ("cmi.core.student_id",   olatStudentId);
    core.sysPut ("cmi.core.student_name", olatStudentName);
    core.sysPut (olatScoCmi);
    core.transBegin();
    isLaunched  = true;
  }

  private  final void olatFinish (boolean commit) {
    if (!isLaunched) return;
    isLaunched = false;
    if (commit) olatCommit(false); // Stupid "implicit commit"
  }

  public  final void olatLaunchAsset (String id) {
    if (isLaunching) return;
    say ("Launching asset: " +id);
    if (isLaunched) {
      say ("SAHS "+olatSahsId +" will be unloaded.");
//      IliasLaunchContent (
//         "../sahs_presentation.php?cmd=unloadSahs"
//          +"&sahs_id="  + olatSahsId
//      );
    } else {
      //clickTime = System.currentTimeMillis();
      olatSahsId     = null;
//      IliasLaunchContent (
//         "../sahs_presentation.php?cmd=launchAsset"
//          +"&ref_id="  + olatRefId
//          +"&asset_id="  + id
//      );
    }
  }

  /**
   *
   * @param isACommit true, if the call comes from a lmscommit, false if it comes from a lmsfinish
   * @return
   */
  private final String olatCommit (boolean isACommit) {
    if (olatSahsId == null) return "false";

    core.transEnd();
    //StringBuilder P = new StringBuilder();
    Hashtable ins = core.getTransNew ();
    Hashtable mod = core.getTransMod ();
    core.transBegin();
   
    LMSDataFormBean lmsDataBean = new LMSDataFormBean();
    lmsDataBean.setItemID(olatSahsId);
    //TODO:gs pass the dataBean for use, and do not get it a second time
    lmsDataBean.setNextAction("5");
    lmsDataBean.setLmsAction("update");
    Map cmiData = new HashMap();
   
    //TODO:gs:c make it possible only to update the changed cmi data.
    if (ins.size() > 0){
      Set set = ins.keySet();
      for(Iterator it = set.iterator();it.hasNext();){
        String cmi = (String)it.next();
        olatScoCmi.remove(cmi);
        olatScoCmi.put(cmi,ins.get(cmi));
      }
    }
    if(mod.size() > 0){
      Set set = mod.keySet();
      for(Iterator it = set.iterator();it.hasNext();){
        String cmi = (String)it.next();
        olatScoCmi.remove(cmi);
        olatScoCmi.put(cmi,mod.get(cmi));
      }
    }
    cmiData.putAll(olatScoCmi);
   
    //work around for missing cmi's (needed by reload code, but not used in ilias code)
    if(cmiData.get("cmi.interactions._count") != null && cmiData.get("cmi.interactions._count") != "0"){
      int count = Integer.parseInt((String)cmiData.get("cmi.interactions._count"));
      for(int i=0;i<count;i++){
        //OLAT-4271: check first if cmi.interactions.n.objectives._count exist before putting a default one
        String objectivesCount = (String)cmiData.get("cmi.interactions."+ i +".objectives._count");
        if(!StringHelper.containsNonWhitespace(objectivesCount)) {
          cmiData.put("cmi.interactions."+ i +".objectives._count","0");
        }
      }
    }
    if (isACommit) {
      String rawScore = (String) cmiData.get(SCORE_IDENT);
      if (rawScore != null && !rawScore.equals("")) {
        // we have a score set in this sco.
        // persist
       
        // to prevent problems with bad xmlhttprequest timings
        synchronized(this) { //o_clusterOK by:fj: instance is spawned by the ScormAPIandDisplayController
          scoresProp.put(olatSahsId, rawScore);
          OutputStream os = null;
          try {
            os = new BufferedOutputStream(new FileOutputStream(scorePropsFile));
            scoresProp.store(os, null);
          } catch (IOException e) {
            throw new OLATRuntimeException(this.getClass(), "could not save scorm-properties-file: "+scorePropsFile.getAbsolutePath(), e);
          }
          finally {
            FileUtils.closeSafely(os);
          }
          // notify
          if (apiCallback != null) {
            apiCallback.lmsCommit(olatSahsId, scoresProp);
          }
        }
      }
    }
   
    lmsDataBean.setDataAsMap(cmiData);
    odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
    odatahandler.updateCMIData(olatSahsId);
    return "true";
  }
 
  /**
   * @return a String that points to the last accessed sco itemId
   */
  public String getScormLastAccessedItemId(){
    LMSDataFormBean lmsDataBean = new LMSDataFormBean();
    lmsDataBean.setLmsAction("boot");
    odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
    LMSResultsBean lmsBean = odatahandler.getResultsBean();
    return lmsBean.getItemID();
  }
 
  /**
   * @param itemId
   * @return true if the item is completed
   */
  public boolean isItemCompleted(String itemId){
    //TODO:gs make method faster by caching lmsBean, but when to set out of date?
    LMSDataFormBean lmsDataBean = new LMSDataFormBean();
    lmsDataBean.setItemID(itemId);
    lmsDataBean.setLmsAction("get");
    odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
    LMSResultsBean lmsBean = odatahandler.getResultsBean();
    return lmsBean.getIsItemCompleted().equals("true");
  }
 
  /**
   * @param itemId
   * @return true if item has any not fullfilled preconditions
   */
  public boolean hasItemPrerequisites(String itemId) {
    //TODO:gs make method faster by caching lmsBean, but when to set out of date?
    LMSDataFormBean lmsDataBean = new LMSDataFormBean();
    lmsDataBean.setItemID(itemId);
    lmsDataBean.setLmsAction("get");
    odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
    LMSResultsBean lmsBean = odatahandler.getResultsBean();
    return lmsBean.getHasPrerequisites().equals("true");
  }
 
  /**
   * @return Map containing the recent sco items status
   */
  public Map getScoItemsStatus(){
    LMSDataFormBean lmsDataBean = new LMSDataFormBean();
    lmsDataBean.setLmsAction("boot");
    odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
    LMSResultsBean lmsBean = odatahandler.getResultsBean();
    String[][] preReqTbl = lmsBean.getPreReqTable();
    Map itemsStatus = new HashMap();
    //put table into map
    for(int i=0; i < preReqTbl.length; i++){
      if(preReqTbl[i][1].equals("not attempted")) preReqTbl[i][1] ="not_attempted";
      itemsStatus.put(preReqTbl[i][0], preReqTbl[i][1]);
    }
    return itemsStatus; 
  }

  /**
   * @param recentId
   * @return the previos Sco itemId
   */
  public Integer getPreviousSco(String recentId) {
//    TODO:gs make method faster by caching lmsBean, but when to set out of date?
    LMSDataFormBean lmsDataBean = new LMSDataFormBean();
    lmsDataBean.setItemID(recentId);
    lmsDataBean.setLmsAction("get");
    odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
    LMSResultsBean lmsBean = odatahandler.getResultsBean();
    String[][] pretable = lmsBean.getPreReqTable();
    String previousNavScoId = "-1";
    for(int i=0; i < pretable.length; i++){
      if(pretable[i][0].equals(recentId) &&  (i != 0 )){
        previousNavScoId =  pretable[--i][0];
        break;
      }
    }
    return new Integer(previousNavScoId);
  }

  /**
   * @param recentId
   * @return the next Sco itemId
   */
  public Integer getNextSco(String recentId) {
//    TODO:gs make method faster by chaching lmsBean, but when to set out of date?
    LMSDataFormBean lmsDataBean = new LMSDataFormBean();
    lmsDataBean.setItemID(recentId);
    lmsDataBean.setLmsAction("get");
    odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler);
    LMSResultsBean lmsBean = odatahandler.getResultsBean();
    String[][] pretable = lmsBean.getPreReqTable();
    String nextNavScoId = "-1";
    for(int i=0; i < pretable.length; i++){
      if(pretable[i][0].equals(recentId) && (i != pretable.length-1)){
        nextNavScoId =  pretable[++i][0];
        break;
      }
    }
    return new Integer(nextNavScoId);
  }
 
  /****************************************************************************************
   * The API functions that an Scorm SCO can call
   *
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSInitialize(java.lang.String)
   */
  public final String LMSInitialize (String s) {
    String rv = core.LMSInitialize(s);
    say(" ----------------- ");
    say ("LMSInitialize("+s+")="+rv);
    if (rv.equals("false")) return rv;
    core.reset();
    rv = core.LMSInitialize(s);
    olatInitialize ();
    return rv;
  }
 
  /**
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSCommit(java.lang.String)
   */
  public final String LMSCommit (String s) {
    String rv = core.LMSCommit(s);
    if (rv.equals("false")) return rv;
    rv = olatCommit(true);
    say ("LMSCommit("+s+")="+rv);
    return rv;
  }
 
  /**
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSFinish(java.lang.String)
   */
  public final String LMSFinish (String s) {
    String rv = core.LMSFinish(s);
    say ("LMSFinish("+s+")="+rv);
    say(" ----------------- ");
    if (rv.equals("false")) return rv;
    olatFinish(true);
    core.reset();
    return rv;
  }
 
  /**
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSGetDiagnostic(java.lang.String)
   */
  public final String LMSGetDiagnostic (String e) {
    String rv = core.LMSGetDiagnostic (e);
    //say ("LMSGetDiagnostic("+e+")="+rv);
    return rv;
  }
 
  /**
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSGetErrorString(java.lang.String)
   */
  public final String LMSGetErrorString (String e) {
    String rv = core.LMSGetErrorString (e);
    say ("LMSGetErrorString("+e+")="+rv);
    return rv;
  }
 
  /**
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSGetLastError()
   */
  public final String LMSGetLastError () {
    String rv = core.LMSGetLastError ();
    //say ("LMSLastError()="+rv);
    return rv;
  }
 
  /**
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSGetValue(java.lang.String)
   */
  public final String LMSGetValue (String l) {
    String rv = core.LMSGetValue (l);
    say ("LMSGetValue("+l+")="+rv);
    return rv;
  }
 
  /**
   * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSSetValue(java.lang.String, java.lang.String)
   */
  public final String LMSSetValue (String l, String r) {
    // BEGIN SABA PUBLISHER conformity hack:
    String lowerL = l.toLowerCase();
    if (lowerL.endsWith(".text")) {
      // undefined SCROM attribute
      return "true";
    } else if (lowerL.endsWith(".pattern") || lowerL.endsWith(".student_response")) {
      // checkbox values have to be in lower case and comma separated!
      String [] tmp = r.toLowerCase().trim().split("[\\s,]");      // lower all, trim and split on all non-whitespace
      StringBuilder sb = new StringBuilder();                        // characters and commas
      for (int i = 0; i < tmp.length; ++i) {                      // for all values
        if (tmp[i] != null && tmp[i].length() > 0)                // ... which are not empty
          sb.append(",").append(tmp[i]);                          // concat it incl. leading comma
      }
      r = sb.toString().substring(1);                              // erase leading comma
    }
    // END SABA PUBLISHER conformity hack
    String rv = core.LMSSetValue (l, r);
    say ("LMSSetValue("+l+"="+r+")="+rv);
    return rv;
  }
}
TOP

Related Classes of org.olat.modules.scorm.OLATApiAdapter

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.