/****************************************************************************** ADL SCORM 2004 4th Edition Sample Run-Time Environment The ADL SCORM 2004 4th Ed. Sample Run-Time Environment is licensed under Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States. The Advanced Distributed Learning Initiative allows you to: * Share - to copy, distribute and transmit the work. * Remix - to adapt the work. Under the following conditions: * Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). * Noncommercial. You may not use this work for commercial purposes. * Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one. For any reuse or distribution, you must make clear to others the license terms of this work. Any of the above conditions can be waived if you get permission from the ADL Initiative. Nothing in this license impairs or restricts the author's moral rights. ******************************************************************************/ package org.adl.datamodels.ieee; import org.adl.util.MessageCollection; import org.adl.datamodels.DataModel; import org.adl.datamodels.DMElement; import org.adl.datamodels.DMElementDescriptor; import org.adl.datamodels.DMErrorCodes; import org.adl.datamodels.DMDelimiterDescriptor; import org.adl.datamodels.DMProcessingInfo; import org.adl.datamodels.DMRequest; import org.adl.datamodels.DMTimeUtility; import org.adl.datamodels.RequestDelimiter; import org.adl.datamodels.RequestToken; import org.adl.datamodels.datatypes.DateTimeValidator; import org.adl.datamodels.datatypes.DurationValidator; import org.adl.datamodels.datatypes.LangStringValidator; import org.adl.datamodels.datatypes.RealRangeValidator; import org.adl.datamodels.datatypes.ResultValidator; import org.adl.datamodels.datatypes.SPMRangeValidator; import org.adl.datamodels.datatypes.URIValidator; import org.adl.datamodels.datatypes.VocabularyValidator; import java.util.Hashtable; import java.util.Vector; import java.io.Serializable; /** * Filename: SCORM_2004_DM.java

* * Description: This class implements the set of data model * elements defined in the SCORM 2004. * * @author ADL Technical Team */ public class SCORM_2004_DM extends DataModel implements Serializable { /** * Constant for the smallest permitted maximum for suspend_data */ public static final int SUSPEND_DATA_SPM = 64000; /** * Constant for smallest permitted maximum for Comments From LMS */ public static final int COMMENTS_FROM_LMS_SPM = 100; /** * Constant for smallest permitted maximum for Location */ public static final int LOCATION_SPM = 1000; /** * Constant for smallest permitted maximum for Objectives */ public static final int OBJECTIVES_SPM = 100; /** * Describes the dot-notation binding string for this data model. */ private String mBinding = "cmi"; /** * Describes the data model elements managed by this data model. */ private Hashtable mElements = null; /** * Default constructor required for serialization support. */ public SCORM_2004_DM() { Vector children = null; Vector subchildren = null; SCORM_2004_DMElement element = null; DMElementDescriptor desc = null; DMDelimiterDescriptor del = null; mElements = new Hashtable(); // Create and add the _version element to the data model Version version = new Version("1.0"); // Add description of this element desc = new DMElementDescriptor("_version", null, null); version.setDescription(desc); mElements.put(version.getDMElementBindingString(), version); // comments_from_learner children = new Vector(); // comment desc = new DMElementDescriptor("comment", null, LONG_SPM, new SPMRangeValidator(LONG_SPM)); del = new DMDelimiterDescriptor("lang", "en", SHORT_SPM, new LangStringValidator()); desc.mDelimiters = new Vector(); desc.mDelimiters.add(del); children.add(desc); // location desc = new DMElementDescriptor("location", null, SHORT_SPM, new SPMRangeValidator(SHORT_SPM)); children.add(desc); // date_time desc = new DMElementDescriptor("timestamp", null, new DateTimeValidator(true)); children.add(desc); desc = new DMElementDescriptor("comments_from_learner", children, 250); // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // comments_from_lms children = new Vector(); // comment desc = new DMElementDescriptor("comment", null, LONG_SPM, new SPMRangeValidator(LONG_SPM)); del = new DMDelimiterDescriptor("lang", "en", SHORT_SPM, new LangStringValidator()); desc.mDelimiters = new Vector(); desc.mDelimiters.add(del); desc.mIsWriteable = false; children.add(desc); // location desc = new DMElementDescriptor("location", null, SHORT_SPM, new SPMRangeValidator(SHORT_SPM)); desc.mIsWriteable = false; children.add(desc); // date_time desc = new DMElementDescriptor("timestamp", null, new DateTimeValidator(true)); desc.mIsWriteable = false; children.add(desc); desc = new DMElementDescriptor("comments_from_lms", children, 100); // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // completion_status String [] vocab = {"unknown", "completed", "not attempted", "incomplete"}; desc = new DMElementDescriptor("completion_status", "unknown", new VocabularyValidator(vocab)); // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // completion_threshold desc = new DMElementDescriptor("completion_threshold", null, new RealRangeValidator(new Double(0.0), new Double(1.0))); desc.mIsWriteable = false; // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // credit String [] creditFormat = {"credit", "no-credit"}; desc = new DMElementDescriptor("credit", "credit", new VocabularyValidator(creditFormat)); desc.mIsWriteable = false; // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // entry String [] entryFormat = {"ab-initio", "resume", ""}; desc = new DMElementDescriptor("entry", "ab-initio", new VocabularyValidator(entryFormat)); desc.mIsWriteable = false; // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // exit String [] exitFormat = {"time-out", "suspend", "logout", "normal", ""}; desc = new DMElementDescriptor("exit", "", new VocabularyValidator(exitFormat)); // exit is write only desc.mIsReadable = false; // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // interactions children = new Vector(); // interaction id desc = new DMElementDescriptor("id", null, LONG_SPM, new URIValidator(LONG_SPM, "long_identifier_type")); children.add(desc); // interaction type String [] typeFormat = {"true-false", "choice", "fill-in", "long-fill-in", "matching", "performance", "sequencing", "likert", "numeric", "other"}; desc = new DMElementDescriptor("type", null, new VocabularyValidator(typeFormat)); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // interaction objectives subchildren = new Vector(); // interaction objectives id desc = new DMElementDescriptor("id", null, LONG_SPM, new URIValidator(LONG_SPM, "long_identifier_type")); desc.mIsUnique = true; subchildren.add(desc); desc = new DMElementDescriptor("objectives", subchildren, 10); desc.mShowChildren = false; desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // interaction timestamp desc = new DMElementDescriptor("timestamp", null, new DateTimeValidator(true)); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // interaction correct_responses subchildren = new Vector(); // interaction correct_responses pattern desc = new DMElementDescriptor("pattern", null, null); subchildren.add(desc); desc = new DMElementDescriptor("correct_responses", subchildren, 250); desc.mShowChildren = false; desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("type")); children.add(desc); // interaction weighting desc = new DMElementDescriptor("weighting", null, new RealRangeValidator(null, null)); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // interaction learner_response desc = new DMElementDescriptor("learner_response", null, null); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("type")); children.add(desc); // interaction result String [] resultFormat = {"correct", "incorrect", "unanticipated", "neutral"}; desc = new DMElementDescriptor("result", null, new ResultValidator(resultFormat)); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // interaction latency desc = new DMElementDescriptor("latency", null, new DurationValidator()); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // interaction description desc = new DMElementDescriptor("description", null, SHORT_SPM, new SPMRangeValidator(SHORT_SPM)); del = new DMDelimiterDescriptor("lang", "en", SHORT_SPM, new LangStringValidator()); desc.mDelimiters = new Vector(); desc.mDelimiters.add(del); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // Finish creating the interaction element and add to the datamodel desc = new DMElementDescriptor("interactions", children, 250); element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // launch_data desc = new DMElementDescriptor("launch_data", null, LONG_SPM, new SPMRangeValidator(LONG_SPM)); desc.mIsWriteable = false; element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // learner_id desc = new DMElementDescriptor("learner_id", null, LONG_SPM, new URIValidator(LONG_SPM, "long_identifier_type")); desc.mIsWriteable = false; element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // learner_name desc = new DMElementDescriptor("learner_name", null, SHORT_SPM, new SPMRangeValidator(SHORT_SPM)); del = new DMDelimiterDescriptor("lang", "en", SHORT_SPM, new LangStringValidator()); desc.mDelimiters = new Vector(); desc.mDelimiters.add(del); desc.mIsWriteable = false; element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // learner_preference children = new Vector(); // learner_preference audio_level desc = new DMElementDescriptor("audio_level", "1", new RealRangeValidator(new Double(0.0), null)); children.add(desc); // learner_preference language desc = new DMElementDescriptor("language", "", SHORT_SPM, new LangStringValidator(true)); children.add(desc); // learner_preference delivery_speed desc = new DMElementDescriptor("delivery_speed", "1", new RealRangeValidator(new Double(0.0), null)); children.add(desc); // learner_preference audio_captioning String [] audioFormat = {"-1", "0", "1"}; desc = new DMElementDescriptor("audio_captioning", "0", new VocabularyValidator(audioFormat)); children.add(desc); // Now add the children to learner_preference and add to the Data Model desc = new DMElementDescriptor("learner_preference", children); element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // location desc = new DMElementDescriptor("location", null, 1000, new SPMRangeValidator(1000)); element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // max_time_allowed desc = new DMElementDescriptor("max_time_allowed", null, new DurationValidator()); desc.mIsWriteable = false; element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // mode String [] modeFormat = {"browse", "normal", "review"}; desc = new DMElementDescriptor("mode", "normal", new VocabularyValidator(modeFormat)); desc.mIsWriteable = false; element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // objectives children = new Vector(); // objectives id desc = new DMElementDescriptor("id", null, LONG_SPM, new URIValidator(LONG_SPM, "long_identifier_type")); desc.mWriteOnce = true; desc.mIsUnique = true; children.add(desc); // objectives score subchildren = new Vector(); // objectives score scaled desc = new DMElementDescriptor("scaled", null, new RealRangeValidator(new Double(-1.0), new Double(1.0))); subchildren.add(desc); // objectives score raw desc = new DMElementDescriptor("raw", null, new RealRangeValidator(null, null)); subchildren.add(desc); // objectives score min desc = new DMElementDescriptor("min", null, new RealRangeValidator(null, null)); subchildren.add(desc); // objectives score max desc = new DMElementDescriptor("max", null, new RealRangeValidator(null, null)); subchildren.add(desc); // Create the score desc = new DMElementDescriptor("score", subchildren); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); // Add the score and its children to the objectives children children.add(desc); // objectives success_status String [] successFormat = {"passed", "failed", "unknown"}; desc = new DMElementDescriptor("success_status", "unknown", new VocabularyValidator(successFormat)); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // objectives completion_status String [] completionFormat = {"completed", "incomplete", "not attempted", "unknown"}; desc = new DMElementDescriptor("completion_status", "unknown", new VocabularyValidator(completionFormat)); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // objectvies progress measure. desc = new DMElementDescriptor("progress_measure", null, new RealRangeValidator(new Double(0), new Double(1.0))); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // objectives description desc = new DMElementDescriptor("description", null, SHORT_SPM, new SPMRangeValidator(SHORT_SPM)); del = new DMDelimiterDescriptor("lang", "en", SHORT_SPM, new LangStringValidator()); desc.mDelimiters = new Vector(); desc.mDelimiters.add(del); desc.mDependentOn = new Vector(); desc.mDependentOn.add(new String("id")); children.add(desc); // Create the objectives element and add it to the data model desc = new DMElementDescriptor("objectives", children, 100); element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // progress_measure desc = new DMElementDescriptor("progress_measure", null, new RealRangeValidator(new Double(0), new Double(1.0))); element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // scaled_passing_score desc = new DMElementDescriptor("scaled_passing_score", null, new RealRangeValidator(new Double(-1.0), new Double(1.0))); desc.mIsWriteable = false; element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // score children = new Vector(); // score scaled desc = new DMElementDescriptor("scaled", null, new RealRangeValidator(new Double(-1.0), new Double(1.0))); children.add(desc); // score raw desc = new DMElementDescriptor("raw", null, new RealRangeValidator(null, null)); children.add(desc); // score min desc = new DMElementDescriptor("min", null, new RealRangeValidator(null, null)); children.add(desc); // score max desc = new DMElementDescriptor("max", null, new RealRangeValidator(null, null)); children.add(desc); // Create the score element and add it to the data model desc = new DMElementDescriptor("score", children); element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // session_time desc = new DMElementDescriptor("session_time", null, new DurationValidator()); desc.mIsReadable = false; element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // success_status desc = new DMElementDescriptor("success_status", "unknown", new VocabularyValidator(successFormat)); // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // suspend_data desc = new DMElementDescriptor("suspend_data", null, SUSPEND_DATA_SPM, new SPMRangeValidator(SUSPEND_DATA_SPM)); element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // time_limit_action String [] actionFormat = {"exit,message", "continue,message", "exit,no message", "continue,no message"}; desc = new DMElementDescriptor("time_limit_action", "continue,no message", new VocabularyValidator(actionFormat)); desc.mIsWriteable = false; // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); // total_time desc = new DMElementDescriptor("total_time", "PT0H0M0S", new DurationValidator()); desc.mIsWriteable = false; // Create and add this element to the data model element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); } /** * Processes an equals() request against this data model. Compares two values * of the same data model element for equality. * * @param iRequest The request (DMRequest) being processed. * @param iValidate Indicates if the provided value should be validated. * * @return A data model error code indicating the result of this * operation. */ public int equals(DMRequest iRequest, boolean iValidate) { // Assume no processing errors int result = DMErrorCodes.NO_ERROR; // Create an 'out' variable DMProcessingInfo pi = new DMProcessingInfo(); // Process this request result = findElement(iRequest, pi); // If we found the 'leaf' element, finish the request if ( result == DMErrorCodes.NO_ERROR ) { RequestToken tok = iRequest.getNextToken(); // Before processing, make sure this is the last token in the request if ( !iRequest.hasMoreTokens() ) { // Make sure this is a Value token if ( tok.getType() == RequestToken.TOKEN_VALUE ) { result = pi.mElement.equals(tok, iValidate); } else { // Wrong type of token -- value expected result = DMErrorCodes.INVALID_REQUEST; } } else { // Too many tokens result = DMErrorCodes.INVALID_REQUEST; } } return result; } /** * Describes this data model's binding string. * * @return This data model's binding string. */ public String getDMBindingString() { return mBinding; } /** * Provides the request data model element. * * @param iElement Describes the requested element's dot-notation bound name. * * @return The DMElement corresponding to the requested element * or null if the element does not exist in the data * model. */ public DMElement getDMElement(String iElement) { DMElement element = (DMElement)mElements.get(iElement); return element; } /** * Processes a GetValue() request against this data model. Retrieves the * value associated with the requested data model element in the current * set of data model values. * * @param iRequest The request (DMRequest) being processed. * @param oInfo Provides the value returned by this request. * * @return A data model error code indicating the result of this * operation. */ public int getValue(DMRequest iRequest, DMProcessingInfo oInfo) { // Assume no processing errors int result = DMErrorCodes.NO_ERROR; // Create an 'out' variable DMProcessingInfo pi = new DMProcessingInfo(); // Process this request result = findElement(iRequest, pi); // If we found the 'leaf' elmeent, finish the request if ( result == DMErrorCodes.NO_ERROR ) { RequestToken tok = iRequest.getNextToken(); // Before processing, make sure this is the last token in the request if ( !iRequest.hasMoreTokens() ) { result = pi.mElement.getValue(tok, iRequest.isAdminRequest(), iRequest.supplyDefaultDelimiters(), oInfo); } else { // Too many tokens result = DMErrorCodes.INVALID_REQUEST; } } return result; } /** * Performs data model specific initialization. * * @return A data model error code indicating the result of this * operation. */ public int initialize() { return DMErrorCodes.NO_ERROR; } /** * Processes a SetValue() request against this data model. This includes a check * for the validity of the value for the specified data model element. * * @param iRequest The request (DMRequest) being processed. * * @return A data model error code indicating the result of this * operation. */ public int setValue(DMRequest iRequest) { return setValue(iRequest, false); } /** * Processes a SetValue() request against this data model. This includes a check * for the validity of the value for the specified data model element. * * @param iRequest The request (DMRequest) being processed. * * @param iSetBySCO bool indicating this value is being set from the sco. * * @return A data model error code indicating the result of this * operation. */ public int setValue(DMRequest iRequest, boolean iSetBySCO) { // Assume no processing errors int result = DMErrorCodes.NO_ERROR; // Create an 'out' variable DMProcessingInfo pi = new DMProcessingInfo(); // Process this request result = findElement(iRequest, pi); // If we found the 'leaf' elmeent, finish the request if ( result == DMErrorCodes.NO_ERROR ) { RequestToken tok = iRequest.getNextToken(); // Before processing, make sure this is the last token in the requset if ( !iRequest.hasMoreTokens() ) { // Make sure this is a Value token if ( tok.getType() == RequestToken.TOKEN_VALUE ) { // Check uniqueness constraints if ( pi.mElement.getDescription() != null ) { if ( pi.mElement.getDescription().mIsUnique ) { if ( !confirmUniqueness(iRequest, tok) ) { // Not Unique result = DMErrorCodes.NOT_UNIQUE; if ( pi.mElement.isInitialized() ) { // Need to check if the current value is different // than the provided value if ( ((SCORM_2004_DMElement)pi.mElement).getInternalValue().equals(tok.getValue()) ) { // Setting the current value to an identical result = DMErrorCodes.NO_ERROR; } } if ( result != DMErrorCodes.NO_ERROR ) { // New storage was initialized but will not be used, delete if ( pi.mRecords != null ) { // Remove the last element from the record set pi.mRecords.remove(pi.mRecords.size() - 1); } } } } if ( result == DMErrorCodes.NO_ERROR && pi.mElement.getDescription().mWriteOnce ) { if ( pi.mElement.isInitialized() ) { // Need to check if the current value is different // than the provided value if ( !(((SCORM_2004_DMElement)pi.mElement). getInternalValue()). equals(tok.getValue()) ) { // Attempt to overwrite ID result = DMErrorCodes.OVERWRITE_ID; } } } } if ( result == DMErrorCodes.NO_ERROR ) { result = pi.mElement.setValue(tok, iRequest.isAdminRequest()); if ( result == DMErrorCodes.NO_ERROR ) { pi.mElement.setBySCO(iSetBySCO); } // If new storage was initialized but not used, delete if ( pi.mRecords != null ) { if ( !(result == DMErrorCodes.NO_ERROR || result == DMErrorCodes.SPM_EXCEEDED) ) { // Remove the last element from the record set pi.mRecords.remove(pi.mRecords.size() - 1); } } } } else { // Wrong type of token -- value expected result = DMErrorCodes.INVALID_REQUEST; } } else { // Too many tokens result = DMErrorCodes.INVALID_REQUEST; } } return result; } /** * Displays the contents of the entire data model. * NOTE: This function has not been implemented. */ public void showAllElements() { // At this time, there is no defined implementation for this method. } /** * Performs data model specific termination. Called when the API processes * a Terminate request from a SCO. * * @return A data model error code indicating the result of this * operation. */ public int terminate() { DMRequest req = null; DMProcessingInfo dmInfo = null; int err = DMErrorCodes.NO_ERROR; req = new DMRequest("cmi.total_time"); req.getNextToken(); dmInfo = new DMProcessingInfo(); err = getValue(req, dmInfo); String totalTime = dmInfo.mValue; req = new DMRequest("cmi.session_time", true, false); req.getNextToken(); dmInfo = new DMProcessingInfo(); err = getValue(req, dmInfo); if ( err == DMErrorCodes.NO_ERROR ) { String sessionTime = dmInfo.mValue; String addedTime = DMTimeUtility.add( totalTime, sessionTime ); req = new DMRequest("cmi.total_time", addedTime, true); req.getNextToken(); err = setValue(req); } // Update completion status req = new DMRequest("cmi.progress_measure"); req.getNextToken(); dmInfo = new DMProcessingInfo(); err = getValue(req, dmInfo); String progress = dmInfo.mValue; req = new DMRequest("cmi.completion_threshold"); req.getNextToken(); dmInfo = new DMProcessingInfo(); err = getValue(req, dmInfo); String threshold = dmInfo.mValue; if ( progress != null && threshold != null && (! progress.equals("")) && (! threshold.equals("")) ) { if ( Double.parseDouble(progress) >= Double.parseDouble(threshold) ) { req = new DMRequest("cmi.completion_status", "completed", true); req.getNextToken(); err = setValue(req); } else if ( Double.parseDouble(progress) < Double.parseDouble(threshold) ) { req = new DMRequest("cmi.completion_status", "incomplete", true); req.getNextToken(); err = setValue(req); } } // Update the success status req = new DMRequest("cmi.score.scaled"); req.getNextToken(); dmInfo = new DMProcessingInfo(); err = getValue(req, dmInfo); progress = dmInfo.mValue; req = new DMRequest("cmi.scaled_passing_score"); req.getNextToken(); dmInfo = new DMProcessingInfo(); err = getValue(req, dmInfo); threshold = dmInfo.mValue; if ( threshold != null && !threshold.equals("") ) { if( progress != null && !progress.equals("") ) { if ( Double.parseDouble(progress) >= Double.parseDouble(threshold) ) { req = new DMRequest("cmi.success_status", "passed", true); req.getNextToken(); err = setValue(req); } else { req = new DMRequest("cmi.success_status", "failed", true); req.getNextToken(); err = setValue(req); } } else { // Reset success_status to unknown req = new DMRequest("cmi.success_status", "unknown", true); req.getNextToken(); err = setValue(req); } } // Set the entry flag based on the exit value req = new DMRequest("cmi.exit", true, false); req.getNextToken(); dmInfo = new DMProcessingInfo(); err = getValue(req, dmInfo); // Check if the current Learner Attempt has been suspended if ( dmInfo.mValue.equals("suspend") || dmInfo.mValue.equals("logout") ) { // The next time this SCO is experienced, the current // Learner Session will be "resumed" req = new DMRequest("cmi.entry", "resume", true); req.getNextToken(); err = setValue(req); // Clear the current Learner Session Time by creating a new element DMElementDescriptor desc = new DMElementDescriptor("session_time", null, new DurationValidator()); desc.mIsReadable = false; SCORM_2004_DMElement element = new SCORM_2004_DMElement(desc, null, this); mElements.put(desc.mBinding, element); } else { req = new DMRequest("cmi.entry", "", true); req.getNextToken(); err = setValue(req); } // This Learner Session is over, clear cmi.exit req = new DMRequest("cmi.exit", "", true); req.getNextToken(); err = setValue(req); return DMErrorCodes.NO_ERROR; } /** * Processes a validate() request against this data model. Checks that * the value of the request is valid for the specified data model element. * * @param iRequest The request (DMRequest) being processed. * * @return A data model error code indicating the result of this * operation. */ public int validate(DMRequest iRequest) { // Assume no processing errors int result = DMErrorCodes.NO_ERROR; // Create an 'out' variable DMProcessingInfo pi = new DMProcessingInfo(); // Process this request result = findElement(iRequest, pi); // If we found the 'leaf' elmeent, finish the request if ( result == DMErrorCodes.NO_ERROR ) { RequestToken tok = iRequest.getNextToken(); // Before processing, make sure this is the last token in the request if ( !iRequest.hasMoreTokens() ) { // Make sure this is a Value token if ( tok.getType() == RequestToken.TOKEN_VALUE ) { result = pi.mElement.validate(tok); } else { // Wrong type of token -- value expected result = DMErrorCodes.INVALID_REQUEST; } } else { // Too many tokens result = DMErrorCodes.INVALID_REQUEST; } } return result; } /** * Determines if the value provided is unique across all instances * of the data model element indicated by the request. * * @param ioRequest The request requiring a uniqueness check. * @param iValue The value being checked. * * @return true if the value is unique for the * given request, otherwise false. */ private boolean confirmUniqueness(DMRequest ioRequest, RequestToken iValue) { boolean unique = true; // Pause the MessageCollection so we don't get SPM errors during // this check. MessageCollection.getInstance().pause(true); // Need to check how many indexes to pass int depth = 0; ioRequest.reset(); while ( ioRequest.hasMoreTokens() ) { RequestToken next = ioRequest.getCurToken(); if ( next.getType() == RequestToken.TOKEN_INDEX ) { depth++; } next = ioRequest.getNextToken(); } // The uniqueness check comes last -- reset the request ioRequest.reset(); // Create the new dot-notation binding string for this check String request = new String(""); RequestToken tok = ioRequest.getNextToken(); request = request + tok.getValue(); // Get the element token tok = ioRequest.getNextToken(); request = request + "." + tok.getValue(); DMProcessingInfo pi = new DMProcessingInfo(); int result = DMErrorCodes.NO_ERROR; int idxCount = 0; // Process tokens until we reach the index while ( ioRequest.hasMoreTokens() && result == DMErrorCodes.NO_ERROR ) { RequestToken next = ioRequest.getCurToken(); if ( next.getType() != RequestToken.TOKEN_INDEX ) { request = request + "." + next.getValue(); next = ioRequest.getNextToken(); } else { idxCount++; if (idxCount == depth) { // Found the correct index token -- Done. break; } request = request + "." + next.getValue(); next = ioRequest.getNextToken(); } } // Skip over the index to find the field be tested tok = ioRequest.getNextToken(); tok = ioRequest.getNextToken(); // How many elements do we need to look at? int count = 0; pi = new DMProcessingInfo(); String newRequest = request + "._count"; DMRequest req = null; try { req = new DMRequest(newRequest); // Eat the dm token req.getNextToken(); // Process the request result = getValue(req, pi); if ( result == DMErrorCodes.NO_ERROR ) { count = Integer.parseInt(pi.mValue); } } catch ( Exception e ) { e.printStackTrace(); } String value = iValue.getValue(); for ( int i = 0 ; i < iValue.getDelimiterCount(); i++ ) { RequestDelimiter del = iValue.getDelimiterAt(i); value = value + del.showDotNotation(); } // Loop across all records, looking at the target field for ( int i = 0; i < (count - 1); i++ ) { newRequest = request + "." + String.valueOf(i) + "." + tok.getValue(); try { req = new DMRequest(newRequest, value); // Eat the dm token req.getNextToken(); result = equals(req, false); if ( result == DMErrorCodes.COMPARE_EQUAL ) { unique = false; break; } } catch ( Exception e ) { // Do nothing } } // Un-pause the MessageCollection MessageCollection.getInstance().pause(false); return unique; } /** * Processes a data model request by finding the target leaf element. * * @param iRequest The request (DMRequest) being processed. * * @param oInfo Provides the value returned by this request. * * @return A data model error code indicating the result of this * operation. */ private int findElement(DMRequest iRequest, DMProcessingInfo oInfo) { // Assume no processing errors int result = DMErrorCodes.NO_ERROR; // Get the first specified element RequestToken tok = iRequest.getNextToken(); if ( tok != null && tok.getType() == RequestToken.TOKEN_ELEMENT ) { DMElement element = (DMElement)mElements.get(tok.getValue()); if ( element != null ) { oInfo.mElement = element; // Check if we need to stop before the last token tok = iRequest.getCurToken(); boolean done = false; if ( tok != null ) { if ( iRequest.isGetValueRequest() ) { if ( tok.getType() == RequestToken.TOKEN_ARGUMENT ) { // We're done done = true; } else if ( tok.getType() == RequestToken.TOKEN_VALUE ) { // Get requests cannot have value tokens result = DMErrorCodes.INVALID_REQUEST; done = true; } } else { if ( tok.getType() == RequestToken.TOKEN_VALUE ) { // We're done done = true; } else if ( tok.getType() == RequestToken.TOKEN_ARGUMENT ) { // Set requests cannot have argument tokens result = DMErrorCodes.INVALID_REQUEST; done = true; } } } // Process remaining tokens while ( !done && iRequest.hasMoreTokens() && result == DMErrorCodes.NO_ERROR ) { result = element.processRequest(iRequest, oInfo); // Move to the next element if processing was successful if ( result == DMErrorCodes.NO_ERROR ) { element = oInfo.mElement; } else { oInfo.mElement = null; } // Check if we need to stop before the last token tok = iRequest.getCurToken(); if ( tok != null ) { if ( iRequest.isGetValueRequest() ) { if ( tok.getType() == RequestToken.TOKEN_ARGUMENT ) { // We're done done = true; } else if ( tok.getType() == RequestToken.TOKEN_VALUE ) { // Get requests cannot have value tokens result = DMErrorCodes.INVALID_REQUEST; done = true; } } else { if ( tok.getType() == RequestToken.TOKEN_VALUE ) { // We're done done = true; } else if ( tok.getType() == RequestToken.TOKEN_ARGUMENT ) { // Set requests cannot have argument tokens result = DMErrorCodes.INVALID_REQUEST; done = true; } } } } } else { // Unknown element result = DMErrorCodes.UNDEFINED_ELEMENT; } } else { // No initial element specified result = DMErrorCodes.INVALID_REQUEST; } // Make sure we are at a leaf element if ( result == DMErrorCodes.NO_ERROR ) { if ( oInfo.mElement.getDescription().mChildren != null) { // Unknown element result = DMErrorCodes.UNDEFINED_ELEMENT; } } return result; } } // end SCORM_2004_DM