diff --git a/modules/boe-module-scorm/pom.xml b/modules/boe-module-scorm/pom.xml new file mode 100644 index 00000000..a17d0493 --- /dev/null +++ b/modules/boe-module-scorm/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + + com.xboe + module + 1.0.0 + + + xboe-module-scorm + xboe-module-scorm + jar + scorm课件的处理 + + 1.8 + + + + com.xboe + xboe-core + + + com.xboe + xboe-module-course + + + javax.servlet + javax.servlet-api + provided + + + commons-validator + commons-validator + 1.6 + + + commons-io + commons-io + 2.6 + + + dom4j + dom4j + 1.6.1 + + + net.sourceforge.cardme + cardme + 0.4.0 + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-text + + + commons-codec + commons-codec + + + org.hibernate + hibernate-core + + + org.projectlombok + lombok + true + + + org.springframework + spring-beans + + + org.springframework + spring-web + + + org.springframework + spring-webmvc + + + org.springframework + spring-aop + + + org.springframework.data + spring-data-jpa + + + org.springframework + spring-aspects + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.fasterxml.jackson.module + jackson-module-parameter-names + + + + org.apache.logging.log4j + log4j-to-slf4j + + + ch.qos.logback + logback-classic + + + + diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/SCORM.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/SCORM.java new file mode 100644 index 00000000..a1056c47 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/SCORM.java @@ -0,0 +1,269 @@ +package com.xboe.module.scorm; + +import java.math.BigDecimal; + +import lombok.extern.slf4j.Slf4j; + +import com.xboe.module.scorm.cam.load.SCORMPackageManager; +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.DeliveryContent; +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.rte.api.LearnerAttempt; +import com.xboe.module.scorm.rte.api.SCORMRuntimeManager; +import com.xboe.module.scorm.rte.model.Objectives.Instance; +import com.xboe.module.scorm.rte.model.RuntimeData; +import com.xboe.module.scorm.sn.api.AttemptManager; +import com.xboe.module.scorm.sn.api.ProcessResult; +import com.xboe.module.scorm.sn.api.SCORMSeqNavManager; +import com.xboe.module.scorm.sn.api.event.EventType; +import com.xboe.module.scorm.sn.api.event.NavigationEvent; +import com.xboe.module.scorm.sn.model.definition.ObjectiveDescription; +import com.xboe.module.scorm.sn.model.tracking.AttemptProgressInformation; +import com.xboe.module.scorm.sn.model.tracking.ObjectiveProgressInformation; +import com.xboe.module.scorm.sn.model.tree.Activity; + +@Slf4j +public class SCORM { + + private static SCORM instance; + + private SCORMPackageManager packageManager; + + private SCORMRuntimeManager runtimeManager; + + private SCORMSeqNavManager snManager; + + private SCORM() { + packageManager = SCORMPackageManager.getInstance(); + runtimeManager = SCORMRuntimeManager.getInstance(); + snManager = SCORMSeqNavManager.getInstance(); + } + + public static SCORM getInstance() { + if (instance == null) { + synchronized (SCORM.class) { + if (instance == null) { + instance = new SCORM(); + } + } + } + return instance; + } + + public SCORMResult process(NavigationEvent event, ID activityTreeID) { + if (event == null || activityTreeID == null) { + return new SCORMResult("Illegal Arguments"); + } + ProcessResult processResult = snManager.process(activityTreeID, event); + if (!processResult.isSuccess()) { + return new SCORMResult(processResult.getErrorMsg()); + } + if (processResult.getDeliveryActivity() == null) { + return new SCORMResult(); + } + Activity deliveryActivity = processResult.getDeliveryActivity(); + ContentPackage contentPackage = packageManager.launch(activityTreeID.getLmsContentPackageID()); + if (contentPackage == null) { + return new SCORMResult(CommonUtils.format("launch content package \"{}\" failed", + activityTreeID.getLmsContentPackageID())); + } + DeliveryContent deliveryContent = contentPackage.getDeliveryContent(deliveryActivity.getId().getIdentifier()); + if (deliveryContent == null) { + return new SCORMResult(CommonUtils.format("obtain delivery content \"{}\" failed", + deliveryActivity.getId().getIdentifier())); + } +// if (!mapTrackingInfoToRuntimeData(deliveryActivity)) { +// return new SCORMResult("sync Tracking Information and Run-time Data failed"); +// } + return new SCORMResult(deliveryContent, deliveryActivity); + } + + public boolean mapTrackingInfoToRuntimeData(Activity activity) { + LearnerAttempt learnerAttempt = runtimeManager.getLearnerAttempt(activity.getId()); + if (learnerAttempt == null) { + log.error("Learner Attempt \"{}\" not exist", activity.getId()); + return false; + } + RuntimeData runtimeData = learnerAttempt.getRuntimeData(); + for (Instance instance : runtimeData.getCmi().getObjectives().getInstances()) { + ObjectiveDescription objectiveDescription = activity.getSequencingDefinition() + .findObjectiveDescriptionByID(instance.getId().getValue()); + ObjectiveProgressInformation information = objectiveDescription.getObjectiveProgressInformation(); + if (information.isObjectiveProgressStatus()) { + instance.getSuccessStatus().setValue(information.isObjectiveSatisfiedStatus() ? "passed" : "failed"); + } + if (information.isObjectiveMeasureStatus()) { + instance.getScore().getScaled().setValue(information.getObjectiveNormalizedMeasure() + .getValue().setScale(7, BigDecimal.ROUND_HALF_UP)); + } + } + AttemptProgressInformation attemptProgressInformation = activity.getAttemptProgressInformation(); + if (attemptProgressInformation.isAttemptProgressStatus()) { + runtimeData.getCmi().getCompletionStatus().getCompletionStatus().setValue(attemptProgressInformation + .isAttemptCompletionStatus() ? "completed" : "incomplete"); + } + if (attemptProgressInformation.isAttemptCompletionAmountStatus()) { + runtimeData.getCmi().getProgressMeasure().getProgressMeasure().setValue(attemptProgressInformation + .getAttemptCompletionAmount().getValue().setScale(7, BigDecimal.ROUND_HALF_UP)); + } + return true; + } + + public void mapRuntimeDataToTrackingInfo(Activity activity) { + if (activity == null) { + return; + } + log.info("mapRuntimeDataToTrackingInfo: {}", activity.getId()); + LearnerAttempt learnerAttempt = runtimeManager.getLearnerAttempt(activity.getId()); + if (learnerAttempt == null) { + log.error("mapRuntimeDataToTrackingInfo: Learner Attempt \"{}\" not exist", activity.getId()); + return; + } + ID attemptManagerID = activity.getBelongActivityTreeID(); + AttemptManager attemptManager = snManager.findAttemptManagerBy(attemptManagerID); + if (attemptManager == null) { + log.error("mapRuntimeDataToTrackingInfo: Attempt Manager \"{}\" not exist", attemptManagerID); + return; + } + if (attemptManager.getLastProcessedEventType() == EventType.Abandon + || attemptManager.getLastProcessedEventType() == EventType.AbandonAll) { + return; + } + RuntimeData runtimeData = learnerAttempt.getRuntimeData(); + AttemptProgressInformation attemptProgressInformation = activity.getAttemptProgressInformation(); + for (Instance instance : runtimeData.getCmi().getObjectives().getInstances()) { + ObjectiveDescription objectiveDescription = activity.getSequencingDefinition() + .findObjectiveDescriptionByID(instance.getId().getValue()); + + if (objectiveDescription == null) { + continue; + } + + ObjectiveProgressInformation information = objectiveDescription.getObjectiveProgressInformation(); + + if (instance.getSuccessStatus().getValue().equals("unknown")) { + information.setObjectiveProgressStatus(false); + information.setObjectiveSatisfiedStatus(false); + } else { + information.setObjectiveProgressStatus(true); + information.setObjectiveSatisfiedStatus("passed".equals(instance.getSuccessStatus().getValue())); + } + + if (instance.getScore().getScaled().getValue() == null) { + information.setObjectiveMeasureStatus(false); + information.setObjectiveNormalizedMeasure(0.0); + } else { + information.setObjectiveMeasureStatus(true); + information.setObjectiveNormalizedMeasure(instance.getScore().getScaled().getValue().doubleValue()); + } + + if (instance.getCompletionStatus().getValue().equals("unknown")) { + information.setObjectiveCompletionProgressStatus(false); + information.setObjectiveCompletionStatus(false); + } else { + information.setObjectiveCompletionProgressStatus(true); + information.setObjectiveCompletionStatus("completed".equals(instance.getCompletionStatus().getValue())); + } + + if (instance.getProgressMeasure().getValue() == null) { + information.setObjectiveCompletionAmountStatus(false); + information.setObjectiveCompletionAmount(0.0); + } else { + information.setObjectiveCompletionAmountStatus(false); + information.setObjectiveCompletionAmount(instance.getProgressMeasure().getValue().doubleValue()); + } + + if (objectiveDescription.isObjectiveContributesToRollup()) { // primary + if (instance.getCompletionStatus().getValue().equals("unknown")) { + attemptProgressInformation.setAttemptProgressStatus(false); + attemptProgressInformation.setAttemptCompletionStatus(false); + } else { + attemptProgressInformation.setAttemptProgressStatus(true); + attemptProgressInformation.setAttemptCompletionStatus( + "completed".equals(instance.getCompletionStatus().getValue())); + } + + if (instance.getProgressMeasure().getValue() == null) { + attemptProgressInformation.setAttemptCompletionAmountStatus(false); + attemptProgressInformation.getAttemptCompletionAmount().setValue(0.0); + } else { + attemptProgressInformation.setAttemptCompletionAmountStatus(true); + attemptProgressInformation.getAttemptCompletionAmount().setValue( + instance.getProgressMeasure().getValue().doubleValue()); + } + } + } + + if (runtimeData.getCmi().getCompletionStatus().getCompletionStatus().getValue().equals("unknown")) { + attemptProgressInformation.setAttemptProgressStatus(false); + attemptProgressInformation.setAttemptCompletionStatus(false); + } else { + attemptProgressInformation.setAttemptProgressStatus(true); + attemptProgressInformation.setAttemptCompletionStatus( + "completed".equals(runtimeData.getCmi().getCompletionStatus().getCompletionStatus().getValue())); + } + if (runtimeData.getCmi().getProgressMeasure().getProgressMeasure().getValue() == null) { + attemptProgressInformation.setAttemptCompletionAmountStatus(false); + attemptProgressInformation.getAttemptCompletionAmount().setValue(0.0); + } else { + attemptProgressInformation.setAttemptCompletionAmountStatus(true); + attemptProgressInformation.getAttemptCompletionAmount().setValue( + runtimeData.getCmi().getProgressMeasure().getProgressMeasure().getValue().doubleValue()); + } + + ObjectiveDescription objectiveDescription = activity.getSequencingDefinition().getPrimaryObjectiveDescription(); + + if (objectiveDescription == null) { + return; + } + + ObjectiveProgressInformation information = objectiveDescription.getObjectiveProgressInformation(); + + if (runtimeData.getCmi().getSuccessStatus().getSuccessStatus().getValue().equals("unknown")) { + information.setObjectiveProgressStatus(false); + information.setObjectiveSatisfiedStatus(false); + } else { + information.setObjectiveProgressStatus(true); + information.setObjectiveSatisfiedStatus( + "passed".equals(runtimeData.getCmi().getSuccessStatus().getSuccessStatus().getValue())); + } + + if (runtimeData.getCmi().getScore().getScaled().getValue() == null) { + information.setObjectiveMeasureStatus(false); + information.setObjectiveNormalizedMeasure(0.0); + } else { + information.setObjectiveMeasureStatus(true); + information.setObjectiveNormalizedMeasure(runtimeData.getCmi().getScore().getScaled().getValue().doubleValue()); + } + + if (runtimeData.getCmi().getCompletionStatus().getCompletionStatus().getValue().equals("unknown")) { + information.setObjectiveCompletionProgressStatus(false); + information.setObjectiveCompletionStatus(false); + } else { + information.setObjectiveCompletionProgressStatus(true); + information.setObjectiveCompletionStatus("completed".equals(runtimeData.getCmi().getCompletionStatus().getCompletionStatus().getValue())); + } + + if (runtimeData.getCmi().getProgressMeasure().getProgressMeasure().getValue() == null) { + information.setObjectiveCompletionAmountStatus(false); + information.setObjectiveCompletionAmount(0.0); + } else { + information.setObjectiveCompletionAmountStatus(false); + information.setObjectiveCompletionAmount(runtimeData.getCmi().getProgressMeasure().getProgressMeasure().getValue().doubleValue()); + } + } + + public SCORMPackageManager getPackageManager() { + return packageManager; + } + + public SCORMRuntimeManager getRuntimeManager() { + return runtimeManager; + } + + public SCORMSeqNavManager getSnManager() { + return snManager; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/SCORMResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/SCORMResult.java new file mode 100644 index 00000000..705da653 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/SCORMResult.java @@ -0,0 +1,50 @@ +package com.xboe.module.scorm; + +import com.xboe.module.scorm.cam.model.DeliveryContent; +import com.xboe.module.scorm.sn.model.tree.Activity; + +public class SCORMResult { + + private final boolean success; + + private final DeliveryContent deliveryContent; + + private final Activity deliveryActivity; + + private final String errorMsg; + + public SCORMResult() { + this(true, null, null, ""); + } + + public SCORMResult(DeliveryContent deliveryContent, Activity deliveryActivity) { + this(true, deliveryContent, deliveryActivity, ""); + } + + public SCORMResult(String errorMsg) { + this(false, null, null, errorMsg); + } + + public SCORMResult(boolean success, DeliveryContent deliveryContent, Activity deliveryActivity, String errorMsg) { + this.success = success; + this.deliveryContent = deliveryContent; + this.deliveryActivity = deliveryActivity; + this.errorMsg = errorMsg; + } + + public boolean isSuccess() { + return success; + } + + public DeliveryContent getDeliveryContent() { + return deliveryContent; + } + + public Activity getDeliveryActivity() { + return deliveryActivity; + } + + public String getErrorMsg() { + return errorMsg; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/Constants.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/Constants.java new file mode 100644 index 00000000..c3011221 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/Constants.java @@ -0,0 +1,7 @@ +package com.xboe.module.scorm.cam.load; + +public final class Constants { + + + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ContentPackageGenerator.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ContentPackageGenerator.java new file mode 100644 index 00000000..e83cb073 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ContentPackageGenerator.java @@ -0,0 +1,1658 @@ +package com.xboe.module.scorm.cam.load; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.Namespace; +import org.dom4j.QName; +import org.dom4j.io.SAXReader; + +import lombok.extern.slf4j.Slf4j; + + +import com.xboe.module.scorm.cam.model.AdlseqMapInfo; +import com.xboe.module.scorm.cam.model.AdlseqObjective; +import com.xboe.module.scorm.cam.model.AdlseqObjectives; +import com.xboe.module.scorm.cam.model.Annotation; +import com.xboe.module.scorm.cam.model.Classification; +import com.xboe.module.scorm.cam.model.CompletionThreshold; +import com.xboe.module.scorm.cam.model.ConditionRule; +import com.xboe.module.scorm.cam.model.ConstrainedChoiceConsiderations; +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.Contribute; +import com.xboe.module.scorm.cam.model.ControlMode; +import com.xboe.module.scorm.cam.model.Data; +import com.xboe.module.scorm.cam.model.DateTime; +import com.xboe.module.scorm.cam.model.DeliveryControls; +import com.xboe.module.scorm.cam.model.Dependency; +import com.xboe.module.scorm.cam.model.Duration; +import com.xboe.module.scorm.cam.model.Educational; +import com.xboe.module.scorm.cam.model.General; +import com.xboe.module.scorm.cam.model.HideLMSUI; +import com.xboe.module.scorm.cam.model.Identifier; +import com.xboe.module.scorm.cam.model.Item; +import com.xboe.module.scorm.cam.model.LOM; +import com.xboe.module.scorm.cam.model.LanguageString; +import com.xboe.module.scorm.cam.model.LanguageStrings; +import com.xboe.module.scorm.cam.model.LifeCycle; +import com.xboe.module.scorm.cam.model.LimitConditions; +import com.xboe.module.scorm.cam.model.Manifest; +import com.xboe.module.scorm.cam.model.ManifestMetadata; +import com.xboe.module.scorm.cam.model.MapInfo; +import com.xboe.module.scorm.cam.model.MetaMetadata; +import com.xboe.module.scorm.cam.model.Metadata; +import com.xboe.module.scorm.cam.model.NavigationInterface; +import com.xboe.module.scorm.cam.model.Objective; +import com.xboe.module.scorm.cam.model.Objectives; +import com.xboe.module.scorm.cam.model.OrComposite; +import com.xboe.module.scorm.cam.model.Organization; +import com.xboe.module.scorm.cam.model.Organizations; +import com.xboe.module.scorm.cam.model.Presentation; +import com.xboe.module.scorm.cam.model.RandomizationControls; +import com.xboe.module.scorm.cam.model.Relation; +import com.xboe.module.scorm.cam.model.RelationResource; +import com.xboe.module.scorm.cam.model.Requirement; +import com.xboe.module.scorm.cam.model.Resource; +import com.xboe.module.scorm.cam.model.Resources; +import com.xboe.module.scorm.cam.model.Rights; +import com.xboe.module.scorm.cam.model.RollupCondition; +import com.xboe.module.scorm.cam.model.RollupConditions; +import com.xboe.module.scorm.cam.model.RollupConsiderations; +import com.xboe.module.scorm.cam.model.RollupRule; +import com.xboe.module.scorm.cam.model.RollupRules; +import com.xboe.module.scorm.cam.model.RuleAction; +import com.xboe.module.scorm.cam.model.RuleCondition; +import com.xboe.module.scorm.cam.model.RuleConditions; +import com.xboe.module.scorm.cam.model.Sequencing; +import com.xboe.module.scorm.cam.model.SequencingCollection; +import com.xboe.module.scorm.cam.model.SequencingRules; +import com.xboe.module.scorm.cam.model.Taxon; +import com.xboe.module.scorm.cam.model.TaxonPath; +import com.xboe.module.scorm.cam.model.Technical; +import com.xboe.module.scorm.cam.model.Vocabulary; +import com.xboe.module.scorm.cam.model.datatype.AnyURI; +import com.xboe.module.scorm.cam.model.datatype.Decimal; +import com.xboe.module.scorm.cam.model.datatype.ID; +import com.xboe.module.scorm.cam.model.datatype.IDRef; +import com.xboe.module.scorm.cam.model.datatype.NonNegativeInteger; +import com.xboe.module.scorm.cam.model.datatype.Token; +import com.xboe.module.scorm.cam.model.datatype.VCard; +import com.xboe.module.scorm.common.CommonUtils; + +@Slf4j +public class ContentPackageGenerator { + + private static final String NAMESPACE_IMSCP = "http://www.imsglobal.org/xsd/imscp_rootv1p1p2"; + private static final String PREFIX_IMSCP = "imscp"; + + private static final String NAMESPACE_ADLCP = "http://www.adlnet.org/xsd/adlcp_rootv1p1p2"; + private static final String PREFIX_ADLCP = "adlcp"; + + private static final String NAMESPACE_ADLSEQ = "http://www.adlnet.org/xsd/adlseq_rootv1p1p2"; + private static final String PREFIX_ADLSEQ = "adlseq"; + + private static final String NAMESPACE_ADLNAV = "http://www.adlnet.org/xsd/adlnav_rootv1p1p2"; + private static final String PREFIX_ADLNAV = "adlnav"; + + private static final String NAMESPACE_IMSSS = "http://www.imsglobal.org/xsd/imsss"; + private static final String PREFIX_IMSSS = "imsss"; + + private static final String NAMESPACE_LOM = "http://ltsc.ieee.org/xsd/LOM"; + private static final String PREFIX_LOM = "lom"; + + private static final String NAMESPACE_XML = "http://www.w3.org/2001/XMLSchema-instance"; + private static final String PREFIX_XML = "xml"; + + private static final String MANIFEST_FILE_NAME = "imsmanifest.xml"; + + private static final boolean CHECK_NAMESPACE=false;//是否检查namespace + + private QNameGenerator rootQNameGenerator; + private ContentPackage contentPackage; + private String scormPkgDir; + + public ContentPackage generateContentPackageFromFile(String scormPkgDir) { + if (scormPkgDir == null) { + log.error("scorm package directory is null"); + return contentPackage; + } + + this.scormPkgDir = scormPkgDir + (scormPkgDir.endsWith("/") ? "" : "/"); + + File scormPkg = new File(scormPkgDir); + if (!scormPkg.exists() || !scormPkg.isDirectory()) { + log.error("Not found directory: {}", scormPkgDir); + return contentPackage; + } + + File manifestXmlFile = new File(this.scormPkgDir + MANIFEST_FILE_NAME); + if (!manifestXmlFile.exists()) { + log.error("Not found {} in {}", MANIFEST_FILE_NAME, scormPkgDir); + return contentPackage; + } + + Document manifestXml; + try { + SAXReader reader = new SAXReader(); + manifestXml = reader.read(manifestXmlFile); + + } catch (DocumentException e) { + log.error("read {} exception: {}", MANIFEST_FILE_NAME, CommonUtils.stringifyError(e)); + return contentPackage; + } + if (manifestXml == null) { + log.error("parse {} error", MANIFEST_FILE_NAME); + return contentPackage; + } + + contentPackage = new ContentPackage(scormPkgDir); + Element manifestNode = manifestXml.getRootElement(); + + // #1: 确定本次解析的名称空间 + rootQNameGenerator = parseNamespace(manifestNode); + + Element orgs=manifestNode.element("organizations"); +// System.out.println(rootQNameGenerator.toString()); +// System.out.println(rootQNameGenerator.imscp("organizations")); + //System.out.println(orgs.asXML()); + +// if (rootQNameGenerator == null) { +// log.error("inner error: parse namespace failed"); +// return contentPackage; +// } + + // #2: manifest + parseManifest(manifestNode); + + // #3: manifest.metadata + //parseManifestMetadata(manifestNode.element(rootQNameGenerator.imscp("metadata"))); + parseManifestMetadata(manifestNode.element("metadata")); + + // #4: manifest.organizations + //parseOrganizations(manifestNode.element(rootQNameGenerator.imscp("organizations"))); + parseOrganizations(manifestNode.element("organizations")); + + // #5: manifest.resources + //parseResources(manifestNode.element(rootQNameGenerator.imscp("resources"))); + parseResources(manifestNode.element("resources")); + + // #6: manifest.sequencingCollection + //parseSequencingCollection(manifestNode.element(rootQNameGenerator.imsss("sequencingCollection"))); + parseSequencingCollection(manifestNode.element("sequencingCollection")); + + + // #7: generate dependency + generateDependency(); + + return contentPackage; + } + + private void generateDependency() { + for (Organization organization : contentPackage.getManifest().getOrganizations().getOrganizationList()) { + for (Item item : organization.getItemList()) { + item.setParentOrganization(organization); + generateDependency(item); + } + } + } + + private void generateDependency(Item item) { + for (Item childItem : item.getItemList()) { + childItem.setParentItem(item); + generateDependency(childItem); + } + } + + private QNameGenerator parseNamespace(Element node) { + if (node == null) { + return null; + } + Map namespaceMap = new HashMap<>(); + node.content().forEach(obj -> { + if (obj instanceof Namespace) { + Namespace namespace = (Namespace) obj; + namespaceMap.put(namespace.getURI(), namespace); + //System.out.println("namespace="+namespace.asXML()); + } + }); + return new QNameGenerator(namespaceMap); + } + + private void parseManifest(Element manifestNode) { + Manifest manifest = new Manifest(); + Namespace pns = manifestNode.getNamespace(); + String id = manifestNode.attributeValue(rootQNameGenerator.imscp("identifier", pns)); + manifest.setIdentifier(id == null ? null : new ID(id)); + manifest.setVersion(manifestNode.attributeValue( + rootQNameGenerator.imscp("version", pns))); + String base = manifestNode.attributeValue(rootQNameGenerator.xml("base", pns)); + manifest.setXmlBase(base == null ? null : new AnyURI(base)); + contentPackage.setManifest(manifest); + } + + private void parseManifestMetadata(Element manifestMetadataNode) { + if (manifestMetadataNode != null) { + ManifestMetadata manifestMetadata = new ManifestMetadata(); + manifestMetadata.setSchema(manifestMetadataNode.elementTextTrim(rootQNameGenerator.imscp("schema"))); + manifestMetadata.setSchemaVersion(manifestMetadataNode.elementText(rootQNameGenerator.imscp("schemaversion"))); + manifestMetadata.setMetadata(parseMetadata(manifestMetadataNode)); + contentPackage.getManifest().setMetadata(manifestMetadata); + } + } + + private void parseOrganizations(Element organizationsNode) { + if (organizationsNode != null) { + Organizations organizations = new Organizations(); + String doi = organizationsNode.attributeValue( rootQNameGenerator.imscp("default", organizationsNode.getNamespace())); + +// List list=(List)organizationsNode.elements(rootQNameGenerator.imscp("organization")); +// for(Element ele : list) { +// System.out.println(ele.asXML()); +// System.out.println("title="+ele.elementText("title")); +// parseOrganization(organizations, ele); +// } +// + + organizations.setDefaultOrganizationID(doi == null ? null : new IDRef(doi)); + organizationsNode.elements(rootQNameGenerator.imscp("organization")).forEach(element -> + parseOrganization(organizations, (Element)element) + ); + contentPackage.getManifest().setOrganizations(organizations); + } + } + + private void parseSequencingCollection(Element sequencingCollectionNode) { + if (sequencingCollectionNode != null) { + SequencingCollection sequencingCollection = new SequencingCollection(); + for (Object element : sequencingCollectionNode.elements("sequencing")) { + Sequencing sequencing = parseSequencing((Element) element); + if (sequencing != null) { + sequencingCollection.getSequencingList().add(sequencing); + } + } + contentPackage.getManifest().setSequencingCollection(sequencingCollection); + } + } + + private void parseOrganization(Organizations organizations, Element organizationNode) { + if (organizationNode != null) { + Organization organization = new Organization(); + Namespace pns = organizationNode.getNamespace(); + String id = organizationNode.attributeValue(rootQNameGenerator.imscp("identifier", pns)); + organization.setIdentifier(id == null ? null : new ID(id)); + String structure = organizationNode.attributeValue(rootQNameGenerator.imscp("structure", pns)); + if (structure != null) { + organization.setStructure(structure); + } + Boolean ogts = parseBoolean(organizationNode.attributeValue( + rootQNameGenerator.adlseq("objectivesGlobalToSystem", pns))); + if (ogts != null) { + organization.setObjectivesGlobalToSystem(ogts); + } + Boolean sdgts = parseBoolean(organizationNode.attributeValue( + rootQNameGenerator.adlcp("sharedDataGlobalToSystem", pns))); + if (sdgts != null) { + organization.setSharedDataGlobalToSystem(sdgts); + } + //String title=organizationNode.elementTextTrim(rootQNameGenerator.imscp("title")); + String title=organizationNode.elementTextTrim("title"); + organization.setTitle(title); + organizationNode.elements(rootQNameGenerator.imscp("item")) + .forEach(element -> parseItem(organization, (Element) element)); + parseCompletionThreshold(organization, organizationNode.element( + rootQNameGenerator.adlcp("completionThreshold"))); + organization.setMetadata(parseMetadata(organizationNode.element(rootQNameGenerator.imscp("metadata")))); + organization.setSequencing(parseSequencing(organizationNode.element( + rootQNameGenerator.imsss("sequencing")))); + organizations.getOrganizationList().add(organization); + } + } + + private void parseItem(Organization organization, Element itemNode) { + if (itemNode != null) { + Item item = parseItem(itemNode); + if (item != null) { + organization.getItemList().add(item); + } + } + } + + private void parseItem(Item item, Element itemNode) { + if (itemNode != null) { + Item childItem = parseItem(itemNode); + if (childItem != null) { + item.getItemList().add(childItem); + } + } + } + + private Item parseItem(Element itemNode) { + if (itemNode != null) { + Item item = new Item(); + Namespace pns = itemNode.getNamespace(); + String id = itemNode.attributeValue(rootQNameGenerator.imscp("identifier", pns)); + item.setIdentifier(id == null ? null : new ID(id)); + item.setIdentifierref(itemNode.attributeValue(rootQNameGenerator.imscp("identifierref", pns))); + Boolean isvisible = parseBoolean(itemNode.attributeValue(rootQNameGenerator.imscp("isvisible", pns))); + if (isvisible != null) { + item.setIsvisible(isvisible); + } + item.setParameters(itemNode.attributeValue(rootQNameGenerator.imscp("parameters", pns))); + item.setTitle(itemNode.elementTextTrim(rootQNameGenerator.imscp("title"))); + itemNode.elements(rootQNameGenerator.imscp("item")) + .forEach(element -> parseItem(item, (Element) element)); + item.setTimeLimitAction(itemNode.elementTextTrim(rootQNameGenerator.adlcp("timeLimitAction"))); + item.setDataFromLMS(itemNode.elementTextTrim(rootQNameGenerator.adlcp("dataFromLMS"))); + parseCompletionThreshold(item, itemNode.element(rootQNameGenerator.adlcp("completionThreshold"))); + parseData(item, itemNode.element(rootQNameGenerator.adlcp("data"))); + item.setMetadata(parseMetadata(itemNode.element(rootQNameGenerator.imscp("metadata")))); + item.setSequencing(parseSequencing(itemNode.element(rootQNameGenerator.imsss("sequencing")))); + parsePresentation(item, itemNode.element(rootQNameGenerator.adlnav("presentation"))); + return item; + } + return null; + } + + private void parsePresentation(Item item, Element presentationNode) { + if (presentationNode != null) { + Presentation presentation = new Presentation(); + parseNavigationInterface(presentation, presentationNode.element( + rootQNameGenerator.adlnav("navigationInterface"))); + item.setPresentation(presentation); + } + } + + private void parseNavigationInterface(Presentation presentation, Element navigationInterfaceNode) { + if (navigationInterfaceNode != null) { + NavigationInterface navigationInterface = new NavigationInterface(); + for (Object element : navigationInterfaceNode.elements(rootQNameGenerator.adlnav("hideLMSUI"))) { + parseHideLMSUI(navigationInterface, (Element) element); + } + presentation.setNavigationInterface(navigationInterface); + } + } + + private void parseHideLMSUI(NavigationInterface navigationInterface, Element hideLMSUINode) { + if (hideLMSUINode != null) { + HideLMSUI hideLMSUI = new HideLMSUI(); + hideLMSUI.setValue(hideLMSUINode.getTextTrim()); + navigationInterface.getHideLMSUIList().add(hideLMSUI); + } + } + + private void parseCompletionThreshold(Organization organization, Element completionThresholdNode) { + if (completionThresholdNode != null) { + organization.setCompletionThreshold(parseCompletionThreshold(completionThresholdNode)); + } + } + + private void parseCompletionThreshold(Item item, Element completionThresholdNode) { + if (completionThresholdNode != null) { + item.setCompletionThreshold(parseCompletionThreshold(completionThresholdNode)); + } + } + + private CompletionThreshold parseCompletionThreshold(Element completionThresholdNode) { + if (completionThresholdNode != null) { + CompletionThreshold completionThreshold = new CompletionThreshold(); + Namespace pns = completionThresholdNode.getNamespace(); + Boolean cbm = parseBoolean(completionThresholdNode.attributeValue( + rootQNameGenerator.adlcp("completedByMeasure", pns))); + if (cbm != null) { + completionThreshold.setCompletedByMeasure(cbm); + } + Decimal mpm = parseDecimal(completionThresholdNode.attributeValue( + rootQNameGenerator.adlcp("minProgressMeasure", pns)), 4); + if (mpm != null) { + completionThreshold.setMinProgressMeasure(mpm); + } + Decimal pw = parseDecimal(completionThresholdNode.attributeValue( + rootQNameGenerator.adlcp("progressWeight", pns)), 4); + if (pw != null) { + completionThreshold.setProgressWeight(pw); + } + return completionThreshold; + } + return null; + } + + private void parseData(Item item, Element dataNode) { + if (dataNode != null) { + Data data = new Data(); + dataNode.elements(rootQNameGenerator.adlcp("map")) + .forEach(element -> parseMap(data, (Element) element)); + item.setData(data); + } + } + + private void parseMap(Data data, Element mapNode) { + if (mapNode != null) { + com.xboe.module.scorm.cam.model.Map map = new com.xboe.module.scorm.cam.model.Map(); + Namespace pns = mapNode.getNamespace(); + String tid = mapNode.attributeValue(rootQNameGenerator.adlcp("targetID", pns)); + map.setTargetID(tid == null ? null : new AnyURI(tid)); + Boolean rsd = parseBoolean(mapNode.attributeValue(rootQNameGenerator.adlcp("readSharedData", pns))); + if (rsd != null) { + map.setReadSharedData(rsd); + } + Boolean wsd = parseBoolean(mapNode.attributeValue(rootQNameGenerator.adlcp("writeSharedData", pns))); + if (wsd != null) { + map.setWriteSharedData(wsd); + } + data.getMapList().add(map); + } + } + + private void parseResources(Element resourcesNode) { + if (resourcesNode != null) { + Resources resources = new Resources(); + String base = resourcesNode.attributeValue( + rootQNameGenerator.xml("base", resourcesNode.getNamespace())); + resources.setXmlBase(base == null ? null : new AnyURI(base)); + resourcesNode.elements(rootQNameGenerator.imscp("resource")) + .forEach(element -> parseResource(resources, (Element) element)); + contentPackage.getManifest().setResources(resources); + } + } + + private void parseResource(Resources resources, Element resourceNode) { + if (resourceNode != null) { + Resource resource = new Resource(); + Namespace pns = resourceNode.getNamespace(); + String id = resourceNode.attributeValue(rootQNameGenerator.imscp("identifier", pns)); + resource.setIdentifier(id == null ? null : new ID(id)); + resource.setType(resourceNode.attributeValue(rootQNameGenerator.imscp("type", pns))); + resource.setHref(resourceNode.attributeValue(rootQNameGenerator.imscp("href", pns))); + String base = resourceNode.attributeValue(rootQNameGenerator.xml("base", pns)); + resource.setXmlBase(base == null ? null : new AnyURI(base)); + resource.setScormType(resourceNode.attributeValue(rootQNameGenerator.adlcp("scormType", pns))); + String qNameForFile = rootQNameGenerator.imscp("file"); + String qNameForDependency = rootQNameGenerator.imscp("dependency"); + for (Object obj : resourceNode.elements()) { + Element element = (Element) obj; + if (element.getQName().equals(qNameForFile)) { + parseFile(resource, element); + } else if (element.getQName().equals(qNameForDependency)) { + parseDependency(resource, element); + } + } + resource.setMetadata(parseMetadata(resourceNode.element(rootQNameGenerator.imscp("metadata")))); + resources.getResourceList().add(resource); + } + } + + private void parseFile(Resource resource, Element fileNode) { + if (fileNode != null) { + com.xboe.module.scorm.cam.model.File file = new com.xboe.module.scorm.cam.model.File(); + file.setHref(fileNode.attributeValue(rootQNameGenerator.imscp("href", fileNode.getNamespace()))); + file.setMetadata(parseMetadata(fileNode.element(rootQNameGenerator.imscp("metadata")))); + resource.getFileList().add(file); + } + } + + private void parseDependency(Resource resource, Element dependencyNode) { + if (dependencyNode != null) { + Dependency dependency = new Dependency(); + dependency.setIdentifierref(dependencyNode.attributeValue( + rootQNameGenerator.imscp("identifierref", dependencyNode.getNamespace()))); + resource.getDependencyList().add(dependency); + } + } + + private Metadata parseMetadata(Element metadataNode) { + if (metadataNode != null) { + Metadata metadata = new Metadata(); + // inner lom + for (Object element : metadataNode.elements(rootQNameGenerator.lom("lom"))) { + Element lomNode = (Element) element; + Map namespaceMap = new HashMap<>(); + for (Object obj : lomNode.content()) { + if (obj instanceof Namespace) { + namespaceMap.put(((Namespace) obj).getURI(), (Namespace) obj); + } + } + // : map.size == 0 + // : map.size > 0 + QNameGenerator qNameGenerator = new QNameGenerator(rootQNameGenerator, namespaceMap); + LOM lom = parseLOM(lomNode, qNameGenerator); + metadata.getLomList().add(lom); + } + // location + for (Object obj : metadataNode.elements(rootQNameGenerator.adlcp("location"))) { + Element locationNode = (Element) obj; + parseLocation(metadata, locationNode); + } + return metadata; + } + return null; + } + + private void parseLocation(Metadata metadata, Element locationNode) { + if (locationNode != null) { + String location = locationNode.getTextTrim(); + if ("".equals(location)) { + return; + } + metadata.getLocationLomMap().put(location, null); + File lomXmlFile = new File(this.scormPkgDir + location); + if (!lomXmlFile.exists()) { + log.error("Not found {} in {}", location, this.scormPkgDir); + return; + } + Document lomXml; + try { + lomXml = new SAXReader().read(lomXmlFile); + } catch (DocumentException e) { + log.error("read {} exception: {}", location, CommonUtils.stringifyError(e)); + return; + } + if (lomXml == null) { + return; + } + Element lomNode = lomXml.getRootElement(); + QNameGenerator qNameGenerator = parseNamespace(lomNode); + LOM lom = parseLOM(lomNode, qNameGenerator); + if (lom != null) { + metadata.getLocationLomMap().put(location, lom); + } + } + } + + private LOM parseLOM(Element lomNode, QNameGenerator qNameGenerator) { + if (lomNode != null && qNameGenerator != null) { + LOM lom = new LOM(); + parseGeneral(lom, lomNode.element(qNameGenerator.lom("general")), qNameGenerator); + parseLifeCycle(lom, lomNode.element(qNameGenerator.lom("lifeCycle")), qNameGenerator); + parseMetaMetadata(lom, lomNode.element(qNameGenerator.lom("metaMetadata")), qNameGenerator); + parseTechnical(lom, lomNode.element(qNameGenerator.lom("technical")), qNameGenerator); + for (Object element : lomNode.elements(qNameGenerator.lom("educational"))) { + parseEducational(lom, (Element) element, qNameGenerator); + } + parseRights(lom, lomNode.element(qNameGenerator.lom("rights")), qNameGenerator); + for (Object element : lomNode.elements(qNameGenerator.lom("relation"))) { + parseRelation(lom, (Element) element, qNameGenerator); + } + for (Object element : lomNode.elements(qNameGenerator.lom("annotation"))) { + parseAnnotation(lom, (Element) element, qNameGenerator); + } + for (Object element : lomNode.elements(qNameGenerator.lom("classification"))) { + parseClassification(lom, (Element) element, qNameGenerator); + } + } + return null; + } + + private void parseGeneral(LOM lom, Element generalNode, QNameGenerator qNameGenerator) { + if (generalNode != null) { + General general = new General(); + for (Object element : generalNode.elements(qNameGenerator.lom("identifier"))) { + Identifier identifier = parseIdentifier((Element) element, qNameGenerator); + if (identifier != null) { + general.getIdentifierList().add(identifier); + } + } + general.setTitle(parseLanguageStrings(generalNode.element( + qNameGenerator.lom("title")), qNameGenerator)); + for (Object element : generalNode.elements(qNameGenerator.lom("language"))) { + String language = ((Element) element).getTextTrim(); + if (language != null) { + general.getLanguageList().add(language); + } + } + for (Object element : generalNode.elements(qNameGenerator.lom("description"))) { + LanguageStrings description = parseLanguageStrings((Element) element, qNameGenerator); + if (description != null) { + general.getDescriptionList().add(description); + } + } + for (Object element : generalNode.elements(qNameGenerator.lom("keyword"))) { + LanguageStrings keyword = parseLanguageStrings((Element) element, qNameGenerator); + if (keyword != null) { + general.getKeywordList().add(keyword); + } + } + for (Object element : generalNode.elements(qNameGenerator.lom("coverage"))) { + LanguageStrings coverage = parseLanguageStrings((Element) element, qNameGenerator); + if (coverage != null) { + general.getCoverageList().add(coverage); + } + } + general.setStructure(parseVocabulary(generalNode.element( + qNameGenerator.lom("structure")), qNameGenerator)); + general.setAggregationLevel(parseVocabulary(generalNode.element( + qNameGenerator.lom("structure")), qNameGenerator)); + lom.setGeneral(general); + } + } + + private void parseLifeCycle(LOM lom, Element lifeCycleNode, QNameGenerator qNameGenerator) { + if (lifeCycleNode != null) { + LifeCycle lifeCycle = new LifeCycle(); + lifeCycle.setVersion(parseLanguageStrings(lifeCycleNode.element( + qNameGenerator.lom("version")), qNameGenerator)); + lifeCycle.setStatus(parseVocabulary(lifeCycleNode.element( + qNameGenerator.lom("status")), qNameGenerator)); + for (Object element : lifeCycleNode.elements(qNameGenerator.lom("contribute"))) { + Contribute contribute = parseContribute((Element) element, qNameGenerator); + if (contribute != null) { + lifeCycle.getContributeList().add(contribute); + } + } + lom.setLifeCycle(lifeCycle); + } + } + + private void parseMetaMetadata(LOM lom, Element metaMetadataNode, QNameGenerator qNameGenerator) { + if (metaMetadataNode != null) { + MetaMetadata metaMetadata = new MetaMetadata(); + for (Object element : metaMetadataNode.elements(qNameGenerator.lom("identifier"))) { + Identifier identifier = parseIdentifier((Element) element, qNameGenerator); + if (identifier != null) { + metaMetadata.getIdentifierList().add(identifier); + } + } + for (Object element : metaMetadataNode.elements(qNameGenerator.lom("contribute"))) { + Contribute contribute = parseContribute((Element) element, qNameGenerator); + if (contribute != null) { + metaMetadata.getContributeList().add(contribute); + } + } + for (Object element : metaMetadataNode.elements(qNameGenerator.lom("metadataSchema"))) { + String schema = ((Element) element).getTextTrim(); + if (schema != null) { + metaMetadata.getMetadataSchema().add(schema); + } + } + metaMetadata.setLanguage(metaMetadataNode.elementTextTrim(qNameGenerator.lom("language"))); + lom.setMetaMetadata(metaMetadata); + } + } + + private void parseTechnical(LOM lom, Element technicalNode, QNameGenerator qNameGenerator) { + if (technicalNode != null) { + Technical technical = new Technical(); + for (Object element : technicalNode.elements(qNameGenerator.lom("format"))) { + String format = ((Element) element).getTextTrim(); + if (format != null) { + technical.getFormatList().add(format); + } + } + technical.setSize(technicalNode.elementTextTrim(qNameGenerator.lom("size"))); + for (Object element : technicalNode.elements(qNameGenerator.lom("location"))) { + String location = ((Element) element).getTextTrim(); + if (location != null) { + technical.getLocationList().add(location); + } + } + for (Object element : technicalNode.elements(qNameGenerator.lom("requirement"))) { + Requirement requirement = parseRequirement((Element) element, qNameGenerator); + if (requirement != null) { + technical.getRequirementList().add(requirement); + } + } + technical.setInstallationRemarks(parseLanguageStrings(technicalNode.element( + qNameGenerator.lom("installationRemarks")), qNameGenerator)); + technical.setOtherPlatformRequirements(parseLanguageStrings(technicalNode.element( + qNameGenerator.lom("otherPlatformRequirements")), qNameGenerator)); + technical.setDuration(parseDuration(technicalNode.element( + qNameGenerator.lom("duration")), qNameGenerator)); + lom.setTechnical(technical); + } + } + + private void parseEducational(LOM lom, Element educationalNode, QNameGenerator qNameGenerator) { + if (educationalNode != null) { + Educational educational = new Educational(); + educational.setInteractivityType(parseVocabulary(educationalNode.element( + qNameGenerator.lom("interactivityType")), qNameGenerator)); + for (Object element : educationalNode.elements(qNameGenerator.lom("learningResourceType"))) { + Vocabulary lrt = parseVocabulary((Element) element, qNameGenerator); + if (lrt != null) { + educational.getLearningResourceTypeList().add(lrt); + } + } + educational.setInteractivityLevel(parseVocabulary(educationalNode.element( + qNameGenerator.lom("interactivityLevel")), qNameGenerator)); + educational.setSemanticDensity(parseVocabulary(educationalNode.element( + qNameGenerator.lom("semanticDensity")), qNameGenerator)); + for (Object element : educationalNode.elements(qNameGenerator.lom("intendedEndUserRole"))) { + Vocabulary ieur = parseVocabulary((Element) element, qNameGenerator); + if (ieur != null) { + educational.getIntendedEndUserRoleList().add(ieur); + } + } + for (Object element : educationalNode.elements(qNameGenerator.lom("context"))) { + Vocabulary context = parseVocabulary((Element) element, qNameGenerator); + if (context != null) { + educational.getContextList().add(context); + } + } + for (Object element : educationalNode.elements(qNameGenerator.lom("typicalAgeRange"))) { + LanguageStrings tar = parseLanguageStrings((Element) element, qNameGenerator); + if (tar != null) { + educational.getTypicalAgeRangeList().add(tar); + } + } + educational.setDifficulty(parseVocabulary(educationalNode.element( + qNameGenerator.lom("difficulty")), qNameGenerator)); + educational.setTypicalLearningTime(parseDuration(educationalNode.element( + qNameGenerator.lom("typicalLearningTime")), qNameGenerator)); + for (Object element : educationalNode.elements(qNameGenerator.lom("description"))) { + LanguageStrings description = parseLanguageStrings((Element) element, qNameGenerator); + if (description != null) { + educational.getDescriptionList().add(description); + } + } + for (Object element : educationalNode.elements(qNameGenerator.lom("language"))) { + String language = ((Element) element).getTextTrim(); + if (language != null) { + educational.getLanguageList().add(language); + } + } + lom.getEducationalList().add(educational); + } + } + + private void parseRights(LOM lom, Element rightsNode, QNameGenerator qNameGenerator) { + if (rightsNode != null) { + Rights rights = new Rights(); + rights.setCost(parseVocabulary(rightsNode.element(qNameGenerator.lom("cost")), qNameGenerator)); + rights.setCopyrightAndOtherRestrictions(parseVocabulary(rightsNode.element( + qNameGenerator.lom("copyrightAndOtherRestrictions")), qNameGenerator)); + rights.setDescription(parseLanguageStrings(rightsNode.element( + qNameGenerator.lom("description")), qNameGenerator)); + lom.setRights(rights); + } + } + + private void parseRelation(LOM lom, Element relationNode, QNameGenerator qNameGenerator) { + if (relationNode != null) { + Relation relation = new Relation(); + relation.setKind(parseVocabulary(relationNode.element( + qNameGenerator.lom("kind")), qNameGenerator)); + relation.setResource(parseRelationResource(relationNode.element( + qNameGenerator.lom("resource")), qNameGenerator)); + lom.getRelationList().add(relation); + } + } + + private void parseAnnotation(LOM lom, Element annotationNode, QNameGenerator qNameGenerator) { + if (annotationNode != null) { + Annotation annotation = new Annotation(); + String entity = annotationNode.elementTextTrim(qNameGenerator.lom("entity")); + annotation.setEntity(entity == null ? null : new VCard(entity)); + annotation.setDate(parseDateTime(annotationNode.element(qNameGenerator.lom("date")), qNameGenerator)); + annotation.setDescription(parseLanguageStrings(annotationNode.element( + qNameGenerator.lom("description")), qNameGenerator)); + lom.getAnnotationList().add(annotation); + } + } + + private void parseClassification(LOM lom, Element classificationNode, QNameGenerator qNameGenerator) { + if (classificationNode != null) { + Classification classification = new Classification(); + classification.setPurpose(parseVocabulary(classificationNode.element( + qNameGenerator.lom("purpose")), qNameGenerator)); + for (Object element : classificationNode.elements(qNameGenerator.lom("taxonPath"))) { + TaxonPath taxonPath = parseTaxonPath((Element) element, qNameGenerator); + if (taxonPath != null) { + classification.getTaxonPathList().add(taxonPath); + } + } + classification.setDescription(parseLanguageStrings(classificationNode.element( + qNameGenerator.lom("description")), qNameGenerator)); + for (Object element : classificationNode.elements(qNameGenerator.lom("keyword"))) { + LanguageStrings keyword = parseLanguageStrings((Element) element, qNameGenerator); + if (keyword != null) { + classification.getKeywordList().add(keyword); + } + } + lom.getClassificationList().add(classification); + } + } + + private Identifier parseIdentifier(Element identifierNode, QNameGenerator qNameGenerator) { + if (identifierNode != null) { + Identifier identifier = new Identifier(); + identifier.setCatalog(identifierNode.elementTextTrim(qNameGenerator.lom("catalog"))); + identifier.setEntry(identifierNode.elementTextTrim(qNameGenerator.lom("entry"))); + return identifier; + } + return null; + } + + private Contribute parseContribute(Element contributeNode, QNameGenerator qNameGenerator) { + if (contributeNode != null) { + Contribute contribute = new Contribute(); + contribute.setRole(parseVocabulary(contributeNode.element( + qNameGenerator.lom("role")), qNameGenerator)); + for (Object element : contributeNode.elements(qNameGenerator.lom("entity"))) { + String entity = ((Element) element).getTextTrim(); + if (entity != null) { + contribute.getEntityList().add(new VCard(entity)); + } + } + contribute.setDate(parseDateTime(contributeNode.element( + qNameGenerator.lom("date")), qNameGenerator)); + return contribute; + } + return null; + } + + private Requirement parseRequirement(Element requirementNode, QNameGenerator qNameGenerator) { + if (requirementNode != null) { + Requirement requirement = new Requirement(); + for (Object element : requirementNode.elements(qNameGenerator.lom("orComposite"))) { + OrComposite orComposite = parseOrComposite((Element) element, qNameGenerator); + if (orComposite != null) { + requirement.getOrCompositeList().add(orComposite); + } + } + return requirement; + } + return null; + } + + private OrComposite parseOrComposite(Element orCompositeNode, QNameGenerator qNameGenerator) { + if (orCompositeNode != null) { + OrComposite orComposite = new OrComposite(); + orComposite.setType(parseVocabulary(orCompositeNode.element( + qNameGenerator.lom("type")), qNameGenerator)); + orComposite.setName(parseVocabulary(orCompositeNode.element( + qNameGenerator.lom("name")), qNameGenerator)); + orComposite.setMinimumVersion(orCompositeNode.elementTextTrim( + qNameGenerator.lom("minimumVersion"))); + orComposite.setMaximumVersion(orCompositeNode.elementTextTrim( + qNameGenerator.lom("maximumVersion"))); + return orComposite; + } + return null; + } + + private RelationResource parseRelationResource(Element relationResourceNode, QNameGenerator qNameGenerator) { + if (relationResourceNode != null) { + RelationResource relationResource = new RelationResource(); + for (Object element : relationResourceNode.elements(qNameGenerator.lom("identifier"))) { + Identifier identifier = parseIdentifier((Element) element, qNameGenerator); + if (identifier != null) { + relationResource.getIdentifierList().add(identifier); + } + } + for (Object element : relationResourceNode.elements(qNameGenerator.lom("description"))) { + LanguageStrings description = parseLanguageStrings((Element) element, qNameGenerator); + if (description != null) { + relationResource.getDescriptionList().add(description); + } + } + return relationResource; + } + return null; + } + + private TaxonPath parseTaxonPath(Element taxonPathNode, QNameGenerator qNameGenerator) { + if (taxonPathNode != null) { + TaxonPath taxonPath = new TaxonPath(); + taxonPath.setSource(parseLanguageStrings(taxonPathNode.element( + qNameGenerator.lom("taxonPath")), qNameGenerator)); + for (Object element : taxonPathNode.elements(qNameGenerator.lom("taxon"))) { + Taxon taxon = parseTaxon((Element) element, qNameGenerator); + if (taxon != null) { + taxonPath.getTaxonList().add(taxon); + } + } + return taxonPath; + } + return null; + } + + private Taxon parseTaxon(Element taxonNode, QNameGenerator qNameGenerator) { + if (taxonNode != null) { + Taxon taxon = new Taxon(); + taxon.setId(taxonNode.elementTextTrim(qNameGenerator.lom("id"))); + taxon.setEntry(parseLanguageStrings(taxonNode.element(qNameGenerator.lom("entry")), qNameGenerator)); + return taxon; + } + return null; + } + + private LanguageStrings parseLanguageStrings(Element pNode, QNameGenerator qNameGenerator) { + if (pNode != null) { + LanguageStrings languageStrings = new LanguageStrings(); + for (Object element : pNode.elements("string")) { + Element stringNode = (Element) element; + String language = stringNode.attributeValue(qNameGenerator.lom("language", stringNode.getNamespace())); + String text = stringNode.getTextTrim(); + languageStrings.getLanguageStringList().add(new LanguageString(language, text)); + } + return languageStrings; + } + return null; + } + + private Vocabulary parseVocabulary(Element vocabularyNode, QNameGenerator qNameGenerator) { + if (vocabularyNode != null) { + String source = vocabularyNode.elementTextTrim(qNameGenerator.lom("source")); + String value = vocabularyNode.elementTextTrim(qNameGenerator.lom("value")); + return new Vocabulary(source, value); + } + return null; + } + + private DateTime parseDateTime(Element dateNode, QNameGenerator qNameGenerator) { + if (dateNode != null) { + DateTime dateTime = new DateTime(); + dateTime.setDateTime(dateNode.elementTextTrim(qNameGenerator.lom("dateTime"))); + dateTime.setDescription(parseLanguageStrings(dateNode.element( + qNameGenerator.lom("description")), qNameGenerator)); + return dateTime; + } + return null; + } + + private Duration parseDuration(Element durationNode, QNameGenerator qNameGenerator) { + if (durationNode != null) { + Duration duration = new Duration(); + duration.setDuration(durationNode.elementTextTrim(qNameGenerator.lom("duration"))); + duration.setDescription(parseLanguageStrings(durationNode.element( + qNameGenerator.lom("description")), qNameGenerator)); + return duration; + } + return null; + } + + private Sequencing parseSequencing(Element sequencingNode) { + if (sequencingNode != null) { + Sequencing sequencing = new Sequencing(); + Namespace pns = sequencingNode.getNamespace(); + String id = sequencingNode.attributeValue(rootQNameGenerator.imsss("ID", pns)); + sequencing.setId(id == null ? null : new ID(id)); + String idRef = sequencingNode.attributeValue(rootQNameGenerator.imsss("IDRef", pns)); + sequencing.setIdRef(idRef == null ? null : new IDRef(idRef)); + parseControlMode(sequencing, sequencingNode.element(rootQNameGenerator.imsss("controlMode"))); + parseSequencingRules(sequencing, sequencingNode.element(rootQNameGenerator.imsss("sequencingRules"))); + parseLimitConditions(sequencing, sequencingNode.element(rootQNameGenerator.imsss("limitConditions"))); + parseAuxiliaryResource(sequencing, sequencingNode.element(rootQNameGenerator.imsss("auxiliaryResources"))); + parseRollupRules(sequencing, sequencingNode.element(rootQNameGenerator.imsss("rollupRules"))); + parseObjectives(sequencing, sequencingNode.element(rootQNameGenerator.imsss("objectives"))); + parseRandomizationControls(sequencing, sequencingNode.element(rootQNameGenerator.imsss("randomizationControls"))); + parseDeliveryControls(sequencing, sequencingNode.element(rootQNameGenerator.imsss("deliveryControls"))); + parseConstrainedChoiceConsiderations(sequencing, sequencingNode.element(rootQNameGenerator.adlseq("constrainedChoiceConsiderations"))); + parseRollupConsiderations(sequencing, sequencingNode.element(rootQNameGenerator.adlseq("rollupConsiderations"))); + parseAdlseqObjectives(sequencing, sequencingNode.element(rootQNameGenerator.adlseq("objectives"))); + return sequencing; + } + return null; + } + + private void parseControlMode(Sequencing sequencing, Element controlModeNode) { + if (controlModeNode != null) { + ControlMode controlMode = new ControlMode(); + Namespace pns = controlModeNode.getNamespace(); + Boolean choice = parseBoolean(controlModeNode.attributeValue( + rootQNameGenerator.imsss("choice", pns))); + if (choice != null) { + controlMode.setChoice(choice); + } + Boolean choiceExit = parseBoolean(controlModeNode.attributeValue( + rootQNameGenerator.imsss("choiceExit", pns))); + if (choiceExit != null) { + controlMode.setChoiceExit(choiceExit); + } + Boolean flow = parseBoolean(controlModeNode.attributeValue( + rootQNameGenerator.imsss("flow", pns))); + if (flow != null) { + controlMode.setFlow(flow); + } + Boolean forwardOnly = parseBoolean(controlModeNode.attributeValue( + rootQNameGenerator.imsss("forwardOnly", pns))); + if (forwardOnly != null) { + controlMode.setForwardOnly(forwardOnly); + } + Boolean useCurrentAttemptObjectiveInfo = parseBoolean(controlModeNode.attributeValue( + rootQNameGenerator.imsss("useCurrentAttemptObjectiveInfo", pns))); + if (useCurrentAttemptObjectiveInfo != null) { + controlMode.setUseCurrentAttemptObjectiveInfo(useCurrentAttemptObjectiveInfo); + } + Boolean useCurrentAttemptProgressInfo = parseBoolean(controlModeNode.attributeValue( + rootQNameGenerator.imsss("useCurrentAttemptProgressInfo", pns))); + if (useCurrentAttemptProgressInfo != null) { + controlMode.setUseCurrentAttemptProgressInfo(useCurrentAttemptProgressInfo); + } + sequencing.setControlMode(controlMode); + } + } + + private void parseSequencingRules(Sequencing sequencing, Element sequencingRulesNode) { + if (sequencingRulesNode != null) { + SequencingRules sequencingRules = new SequencingRules(); + String pre = rootQNameGenerator.imsss("preConditionRule"); + String exit = rootQNameGenerator.imsss("exitConditionRule"); + String post = rootQNameGenerator.imsss("postConditionRule"); + for (Object element : sequencingRulesNode.elements()) { + Element conditionRuleNode = (Element) element; + QName qn = conditionRuleNode.getQName(); + if (qn.equals(pre)) { + ConditionRule conditionRule = parseConditionRule(conditionRuleNode); + if (conditionRule != null) { + sequencingRules.getPreConditionRuleList().add(conditionRule); + } + } else if (qn.equals(exit)) { + ConditionRule conditionRule = parseConditionRule(conditionRuleNode); + if (conditionRule != null) { + sequencingRules.getExitConditionRuleList().add(conditionRule); + } + } else if (qn.equals(post)) { + ConditionRule conditionRule = parseConditionRule(conditionRuleNode); + if (conditionRule != null) { + sequencingRules.getPostConditionRuleList().add(conditionRule); + } + } + } + sequencing.setSequencingRules(sequencingRules); + } + } + + private void parseLimitConditions(Sequencing sequencing, Element limitConditionsNode) { + if (limitConditionsNode != null) { + LimitConditions limitConditions = new LimitConditions(); + Namespace pns = limitConditionsNode.getNamespace(); + limitConditions.setAttemptLimit(parseNonNegativeInteger(limitConditionsNode.attributeValue( + rootQNameGenerator.imsss("attemptLimit", pns)))); + limitConditions.setAttemptAbsoluteDurationLimit(limitConditionsNode.attributeValue( + rootQNameGenerator.imsss("attemptAbsoluteDurationLimit", pns))); + sequencing.setLimitConditions(limitConditions); + } + } + + private void parseAuxiliaryResource(Sequencing sequencing, Element auxiliaryResourcesNode) { + // don't implementation + } + + private void parseRollupRules(Sequencing sequencing, Element rollupRulesNode) { + if (rollupRulesNode != null) { + RollupRules rollupRules = new RollupRules(); + Namespace pns = rollupRulesNode.getNamespace(); + Boolean ros = parseBoolean(rollupRulesNode.attributeValue( + rootQNameGenerator.imsss("rollupObjectiveSatisfied", pns))); + if (ros != null) { + rollupRules.setRollupObjectiveSatisfied(ros); + } + Boolean rpc = parseBoolean(rollupRulesNode.attributeValue( + rootQNameGenerator.imsss("rollupProgressCompletion", pns))); + if (rpc != null) { + rollupRules.setRollupProgressCompletion(rpc); + } + Decimal omw = parseDecimal(rollupRulesNode.attributeValue( + rootQNameGenerator.imsss("objectiveMeasureWeight", pns)), 4); + if (omw != null) { + rollupRules.setObjectiveMeasureWeight(omw); + } + for (Object element : rollupRulesNode.elements(rootQNameGenerator.imsss("rollupRule"))) { + parseRollupRule(rollupRules, (Element) element); + } + sequencing.setRollupRules(rollupRules); + } + } + + private void parseObjectives(Sequencing sequencing, Element objectivesNode) { + if (objectivesNode != null) { + Objectives objectives = new Objectives(); + objectives.setPrimaryObjective(parseObjective(objectivesNode.element( + rootQNameGenerator.imsss("primaryObjective")))); + for (Object element : objectivesNode.elements(rootQNameGenerator.imsss("objective"))) { + Objective objective = parseObjective((Element) element); + if (objective != null) { + objectives.getObjectiveList().add(objective); + } + } + sequencing.setObjectives(objectives); + } + } + + private void parseRandomizationControls(Sequencing sequencing, Element randomizationControlsNode) { + if (randomizationControlsNode != null) { + RandomizationControls randomizationControls = new RandomizationControls(); + Namespace pns = randomizationControlsNode.getNamespace(); + String rt = randomizationControlsNode.attributeValue( + rootQNameGenerator.imsss("randomizationTiming", pns)); + if (rt != null) { + randomizationControls.setRandomizationTiming(new Token(rt)); + } + randomizationControls.setSelectCount(parseNonNegativeInteger(randomizationControlsNode.attributeValue( + rootQNameGenerator.imsss("selectCount", pns)))); + Boolean rc = parseBoolean(randomizationControlsNode.attributeValue( + rootQNameGenerator.imsss("reorderChildren", pns))); + if (rc != null) { + randomizationControls.setReorderChildren(rc); + } + String st = randomizationControlsNode.attributeValue( + rootQNameGenerator.imsss("selectionTiming", pns)); + if (st != null) { + randomizationControls.setSelectionTiming(new Token(st)); + } + sequencing.setRandomizationControls(randomizationControls); + } + } + + private void parseDeliveryControls(Sequencing sequencing, Element deliveryControlsNode) { + if (deliveryControlsNode != null) { + DeliveryControls deliveryControls = new DeliveryControls(); + Namespace pns = deliveryControlsNode.getNamespace(); + Boolean tracked = parseBoolean(deliveryControlsNode.attributeValue( + rootQNameGenerator.imsss("tracked", pns))); + if (tracked != null) { + deliveryControls.setTracked(tracked); + } + Boolean csbc = parseBoolean(deliveryControlsNode.attributeValue( + rootQNameGenerator.imsss("completionSetByContent", pns))); + if (csbc != null) { + deliveryControls.setCompletionSetByContent(csbc); + } + Boolean osbc = parseBoolean(deliveryControlsNode.attributeValue( + rootQNameGenerator.imsss("objectiveSetByContent", pns))); + if (osbc != null) { + deliveryControls.setObjectiveSetByContent(osbc); + } + sequencing.setDeliveryControls(deliveryControls); + } + } + + private void parseConstrainedChoiceConsiderations(Sequencing sequencing, Element constrainedChoiceConsiderationsNode) { + if (constrainedChoiceConsiderationsNode != null) { + ConstrainedChoiceConsiderations constrainedChoiceConsiderations = new ConstrainedChoiceConsiderations(); + Namespace pns = constrainedChoiceConsiderationsNode.getNamespace(); + Boolean pa = parseBoolean(constrainedChoiceConsiderationsNode.attributeValue( + rootQNameGenerator.adlseq("preventActivation", pns))); + if (pa != null) { + constrainedChoiceConsiderations.setPreventActivation(pa); + } + Boolean cc = parseBoolean(constrainedChoiceConsiderationsNode.attributeValue( + rootQNameGenerator.adlseq("constrainChoice", pns))); + if (cc != null) { + constrainedChoiceConsiderations.setConstrainChoice(cc); + } + sequencing.setConstrainedChoiceConsiderations(constrainedChoiceConsiderations); + } + } + + private void parseRollupConsiderations(Sequencing sequencing, Element rollupConsiderationsNode) { + if (rollupConsiderationsNode != null) { + RollupConsiderations rollupConsiderations = new RollupConsiderations(); + Namespace pns = rollupConsiderationsNode.getNamespace(); + String rfs = rollupConsiderationsNode.attributeValue( + rootQNameGenerator.adlseq("requiredForSatisfied", pns)); + if (rfs != null) { + rollupConsiderations.setRequiredForSatisfied(new Token(rfs)); + } + String rfns = rollupConsiderationsNode.attributeValue( + rootQNameGenerator.adlseq("requiredForNotSatisfied", pns)); + if (rfns != null) { + rollupConsiderations.setRequiredForNotSatisfied(new Token(rfns)); + } + String rfc = rollupConsiderationsNode.attributeValue( + rootQNameGenerator.adlseq("requiredForCompleted", pns)); + if (rfc != null) { + rollupConsiderations.setRequiredForCompleted(new Token(rfc)); + } + String rfi = rollupConsiderationsNode.attributeValue( + rootQNameGenerator.adlseq("requiredForIncomplete", pns)); + if (rfi != null) { + rollupConsiderations.setRequiredForIncomplete(new Token(rfi)); + } + Boolean msia = parseBoolean(rollupConsiderationsNode.attributeValue( + rootQNameGenerator.adlseq("measureSatisfactionIfActive", pns))); + if (msia != null) { + rollupConsiderations.setMeasureSatisfactionIfActive(msia); + } + sequencing.setRollupConsiderations(rollupConsiderations); + } + } + + private void parseAdlseqObjectives(Sequencing sequencing, Element adlseqObjectivesNode) { + if (adlseqObjectivesNode != null) { + AdlseqObjectives adlseqObjectives = new AdlseqObjectives(); + for (Object element : adlseqObjectivesNode.elements(rootQNameGenerator.adlseq("objective"))) { + parseAdlseqObjective(adlseqObjectives, (Element) element); + } + sequencing.setAdlseqObjectives(adlseqObjectives); + } + } + + private ConditionRule parseConditionRule(Element conditionRuleNode) { + if (conditionRuleNode != null) { + ConditionRule conditionRule = new ConditionRule(); + parseRuleConditions(conditionRule, conditionRuleNode.element( + rootQNameGenerator.imsss("ruleConditions"))); + Element actionNode = conditionRuleNode.element(rootQNameGenerator.imsss("ruleAction")); + String action = actionNode == null ? null : actionNode.attributeValue( + rootQNameGenerator.imsss("action", conditionRuleNode.getNamespace())); + RuleAction ruleAction = action == null ? null : new RuleAction(); + if (ruleAction != null) { + ruleAction.setAction(new Token(action)); + } + conditionRule.setRuleAction(ruleAction); + return conditionRule; + } + return null; + } + + private void parseRuleConditions(ConditionRule conditionRule, Element ruleConditionsNode) { + if (ruleConditionsNode != null) { + RuleConditions ruleConditions = new RuleConditions(); + String cc = ruleConditionsNode.attributeValue( + rootQNameGenerator.imsss("conditionCombination", ruleConditionsNode.getNamespace())); + if (cc != null) { + ruleConditions.setConditionCombination(new Token(cc)); + } + for (Object element : ruleConditionsNode.elements(rootQNameGenerator.imsss("ruleCondition"))) { + parseRuleCondition(ruleConditions, (Element) element); + } + conditionRule.setRuleConditions(ruleConditions); + } + } + + private void parseRuleCondition(RuleConditions ruleConditions, Element ruleConditionNode) { + if (ruleConditionNode != null) { + RuleCondition ruleCondition = new RuleCondition(); + Namespace pns = ruleConditionNode.getNamespace(); + String condition = ruleConditionNode.attributeValue(rootQNameGenerator.imsss("condition", pns)); + ruleCondition.setCondition(condition == null ? null : new Token(condition)); + ruleCondition.setReferencedObjective(ruleConditionNode.attributeValue( + rootQNameGenerator.imsss("referencedObjective", pns))); + ruleCondition.setMeasureThreshold(parseDecimal(ruleConditionNode.attributeValue( + rootQNameGenerator.imsss("measureThreshold", pns)), 4)); + String op = ruleConditionNode.attributeValue(rootQNameGenerator.imsss("operator", pns)); + if (op != null) { + ruleCondition.setOperator(new Token(op)); + } + ruleConditions.getRuleConditionList().add(ruleCondition); + } + } + + private void parseRollupRule(RollupRules rollupRules, Element rollupRuleNode) { + if (rollupRuleNode != null) { + RollupRule rollupRule = new RollupRule(); + Namespace pns = rollupRuleNode.getNamespace(); + String cas = rollupRuleNode.attributeValue(rootQNameGenerator.imsss("childActivitySet", pns)); + if (cas != null) { + rollupRule.setChildActivitySet(new Token(cas)); + } + NonNegativeInteger mc = parseNonNegativeInteger(rollupRuleNode.attributeValue( + rootQNameGenerator.imsss("minimumCount", pns))); + if (mc != null) { + rollupRule.setMinimumCount(mc); + } + Decimal mp = parseDecimal(rollupRuleNode.attributeValue( + rootQNameGenerator.imsss("minimumPercent", pns)), 4); + if (mp != null) { + rollupRule.setMinimumPercent(mp); + } + parseRollupConditions(rollupRule, rollupRuleNode.element(rootQNameGenerator.imsss("rollupConditions"))); + Element actionNode = rollupRuleNode.element(rootQNameGenerator.imsss("rollupAction")); + String action = actionNode == null ? null : actionNode.attributeValue( + rootQNameGenerator.imsss("action", pns)); + if (action != null) { + rollupRule.setRollupAction(new Token(action)); + } + rollupRules.getRollupRuleList().add(rollupRule); + } + } + + private void parseRollupConditions(RollupRule rollupRule, Element rollupConditionsNode) { + if (rollupConditionsNode != null) { + RollupConditions rollupConditions = new RollupConditions(); + String cc = rollupConditionsNode.attributeValue( + rootQNameGenerator.imsss("conditionCombination", rollupConditionsNode.getNamespace())); + if (cc != null) { + rollupConditions.setConditionCombination(new Token(cc)); + } + parseRollupCondition(rollupConditions, rollupConditionsNode.element( + rootQNameGenerator.imsss("rollupCondition"))); + rollupRule.setRollupConditions(rollupConditions); + } + } + + private void parseRollupCondition(RollupConditions rollupConditions, Element rollupConditionNode) { + if (rollupConditionNode != null) { + RollupCondition rollupCondition = new RollupCondition(); + Namespace pns = rollupConditionNode.getNamespace(); + String condition = rollupConditionNode.attributeValue(rootQNameGenerator.imsss("condition", pns)); + rollupCondition.setCondition(condition == null ? null : new Token(condition)); + String op = rollupConditionNode.attributeValue(rootQNameGenerator.imsss("operator", pns)); + if (op != null) { + rollupCondition.setOperator(new Token(op)); + } + rollupConditions.getRollupConditionList().add(rollupCondition); + } + } + + private Objective parseObjective(Element objectiveNode) { + if (objectiveNode != null) { + Objective objective = new Objective(); + Namespace pns = objectiveNode.getNamespace(); + Boolean sbm = parseBoolean(objectiveNode.attributeValue( + rootQNameGenerator.imsss("satisfiedByMeasure", pns))); + if (sbm != null) { + objective.setSatisfiedByMeasure(sbm); + } + String oid = objectiveNode.attributeValue(rootQNameGenerator.imsss("objectiveID", pns)); + if (oid != null) { + objective.setObjectiveID(new AnyURI(oid)); + } + Decimal mnm = parseDecimal(objectiveNode.elementTextTrim( + rootQNameGenerator.imsss("minNormalizedMeasure")), 4); + if (mnm != null) { + objective.setMinNormalizedMeasure(mnm); + } + for (Object element : objectiveNode.elements(rootQNameGenerator.imsss("mapInfo"))) { + parseMapInfo(objective, (Element) element); + } + return objective; + } + return null; + } + + private void parseMapInfo(Objective objective, Element mapInfoNode) { + if (mapInfoNode != null) { + MapInfo mapInfo = new MapInfo(); + Namespace pns = mapInfoNode.getNamespace(); + String toid = mapInfoNode.attributeValue(rootQNameGenerator.imsss("targetObjectiveID", pns)); + mapInfo.setTargetObjectiveID(toid == null ? null : new AnyURI(toid)); + Boolean rss = parseBoolean(mapInfoNode.attributeValue( + rootQNameGenerator.imsss("readSatisfiedStatus", pns))); + if (rss != null) { + mapInfo.setReadSatisfiedStatus(rss); + } + Boolean rnm = parseBoolean(mapInfoNode.attributeValue( + rootQNameGenerator.imsss("readNormalizedMeasure", pns))); + if (rnm != null) { + mapInfo.setReadNormalizedMeasure(rnm); + } + Boolean wss = parseBoolean(mapInfoNode.attributeValue( + rootQNameGenerator.imsss("writeSatisfiedStatus", pns))); + if (wss != null) { + mapInfo.setWriteSatisfiedStatus(wss); + } + Boolean wnm = parseBoolean(mapInfoNode.attributeValue( + rootQNameGenerator.imsss("writeNormalizedMeasure", pns))); + if (wnm != null) { + mapInfo.setWriteNormalizedMeasure(wnm); + } + objective.getMapInfoList().add(mapInfo); + } + } + + private void parseAdlseqObjective(AdlseqObjectives adlseqObjectives, Element adlseqObjectiveNode) { + if (adlseqObjectiveNode != null) { + AdlseqObjective adlseqObjective = new AdlseqObjective(); + String oid = adlseqObjectiveNode.attributeValue( + rootQNameGenerator.adlseq("objectiveID", adlseqObjectiveNode.getNamespace())); + adlseqObjective.setObjectiveID(oid == null ? null : new AnyURI(oid)); + for (Object element : adlseqObjectiveNode.elements(rootQNameGenerator.adlseq("mapInfo"))) { + parseAdlSeqMapInfo(adlseqObjective, (Element) element); + } + adlseqObjectives.getObjectiveList().add(adlseqObjective); + } + } + + private void parseAdlSeqMapInfo(AdlseqObjective adlseqObjective, Element adlseqMapInfoNode) { + if (adlseqMapInfoNode != null) { + AdlseqMapInfo adlseqMapInfo = new AdlseqMapInfo(); + Namespace pns = adlseqMapInfoNode.getNamespace(); + String toid = adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("targetObjectiveID", pns)); + adlseqMapInfo.setTargetObjectiveID(toid == null ? null : new AnyURI(toid)); + Boolean readRawScore = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("readRawScore", pns))); + if (readRawScore != null) { + adlseqMapInfo.setReadRawScore(readRawScore); + } + Boolean readMinScore = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("readMinScore", pns))); + if (readMinScore != null) { + adlseqMapInfo.setReadMinScore(readMinScore); + } + Boolean readMaxScore = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("readMaxScore", pns))); + if (readMaxScore != null) { + adlseqMapInfo.setReadMaxScore(readMaxScore); + } + Boolean readCompletionStatus = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("readCompletionStatus", pns))); + if (readCompletionStatus != null) { + adlseqMapInfo.setReadCompletionStatus(readCompletionStatus); + } + Boolean readProgressMeasure = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("readProgressMeasure", pns))); + if (readProgressMeasure != null) { + adlseqMapInfo.setReadProgressMeasure(readProgressMeasure); + } + Boolean writeRawScore = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("writeRawScore", pns))); + if (writeRawScore != null) { + adlseqMapInfo.setWriteRawScore(writeRawScore); + } + Boolean writeMinScore = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("writeMinScore", pns))); + if (writeMinScore != null) { + adlseqMapInfo.setWriteMinScore(writeMinScore); + } + Boolean writeMaxScore = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("writeMaxScore", pns))); + if (writeMaxScore != null) { + adlseqMapInfo.setWriteMaxScore(writeMaxScore); + } + Boolean writeCompletionStatus = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("writeCompletionStatus", pns))); + if (writeCompletionStatus != null) { + adlseqMapInfo.setWriteCompletionStatus(writeCompletionStatus); + } + Boolean writeProgressMeasure = parseBoolean(adlseqMapInfoNode.attributeValue( + rootQNameGenerator.adlseq("writeProgressMeasure", pns))); + if (writeProgressMeasure != null) { + adlseqMapInfo.setWriteProgressMeasure(writeProgressMeasure); + } + adlseqObjective.getMapInfoList().add(adlseqMapInfo); + } + } + + private Boolean parseBoolean(String s) { + if ("true".equalsIgnoreCase(s)) { + return true; + } else if ("false".equalsIgnoreCase(s)) { + return false; + } else { + return null; + } + } + + private Decimal parseDecimal(String s, int scale) { + if (s == null || "".equals(s)) { + return null; + } + try { + Double.parseDouble(s); + } catch (Exception e) { + return null; + } + return new Decimal(s, scale); + } + + private NonNegativeInteger parseNonNegativeInteger(String s) { + if (s == null || "".equals(s)) { + return null; + } + try { + if (Integer.parseInt(s) < 0) { + return null; + } + return new NonNegativeInteger(s); + } catch (Exception e) { + return null; + } + } + + private class QNameGenerator { + private Namespace imscpNamespace; + private Namespace adlcpNamespace; + private Namespace adlseqNamespace; + private Namespace adlnavNamespace; + private Namespace imsssNamespace; + private Namespace lomNamespace; + private Namespace xmlNamespace; + + QNameGenerator(Map namespaceMap) { + imscpNamespace = namespaceMap.getOrDefault(NAMESPACE_IMSCP, new Namespace(PREFIX_IMSCP, NAMESPACE_IMSCP)); + adlcpNamespace = namespaceMap.getOrDefault(NAMESPACE_ADLCP, new Namespace(PREFIX_ADLCP, NAMESPACE_ADLCP)); + adlseqNamespace = namespaceMap.getOrDefault(NAMESPACE_ADLSEQ, new Namespace(PREFIX_ADLSEQ, NAMESPACE_ADLSEQ)); + adlnavNamespace = namespaceMap.getOrDefault(NAMESPACE_ADLNAV, new Namespace(PREFIX_ADLNAV, NAMESPACE_ADLNAV)); + imsssNamespace = namespaceMap.getOrDefault(NAMESPACE_IMSSS, new Namespace(PREFIX_IMSSS, NAMESPACE_IMSSS)); + lomNamespace = namespaceMap.getOrDefault(NAMESPACE_LOM, new Namespace(PREFIX_LOM, NAMESPACE_LOM)); + xmlNamespace = namespaceMap.getOrDefault(NAMESPACE_XML, new Namespace(PREFIX_XML, NAMESPACE_XML)); + } + + QNameGenerator(QNameGenerator baseQNameGenerator, Map namespaceMap) { + imscpNamespace = namespaceMap.getOrDefault(NAMESPACE_IMSCP, baseQNameGenerator.imscpNamespace); + adlcpNamespace = namespaceMap.getOrDefault(NAMESPACE_ADLCP, baseQNameGenerator.adlcpNamespace); + adlseqNamespace = namespaceMap.getOrDefault(NAMESPACE_ADLSEQ, baseQNameGenerator.adlseqNamespace); + adlnavNamespace = namespaceMap.getOrDefault(NAMESPACE_ADLNAV, baseQNameGenerator.adlnavNamespace); + imsssNamespace = namespaceMap.getOrDefault(NAMESPACE_IMSSS, baseQNameGenerator.imsssNamespace); + lomNamespace = namespaceMap.getOrDefault(NAMESPACE_LOM, baseQNameGenerator.lomNamespace); + xmlNamespace = namespaceMap.getOrDefault(NAMESPACE_XML, baseQNameGenerator.xmlNamespace); + } + + String imscp(String elementName) { + //return new QName(elementName, imscpNamespace); + return elementName; + } + + String adlcp(String elementName) { + //return new QName(elementName, adlcpNamespace); + return elementName; + } + + String adlseq(String elementName) { + //return new QName(elementName, adlseqNamespace); + return elementName; + } + + String adlnav(String elementName) { + //return new QName(elementName, adlnavNamespace); + return elementName; + } + + String imsss(String elementName) { + //return new QName(elementName, imsssNamespace); + return elementName; + } + + String lom(String elementName) { + //return new QName(elementName, lomNamespace); + return elementName; + } + + String xml(String elementName) { + //return new QName(elementName, xmlNamespace); + return elementName; + } + + QName imscp(String attributeName, Namespace elementNamespace) { + + if (Objects.equals(elementNamespace, imscpNamespace)) { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } else { + //System.out.println(attributeName); + //System.out.println(imscpNamespace.asXML()); + if(CHECK_NAMESPACE) { + return new QName(attributeName, imscpNamespace); + }else { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } + + } + } + + QName adlcp(String attributeName, Namespace elementNamespace) { + if (Objects.equals(elementNamespace, adlcpNamespace)) { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } else { + if(CHECK_NAMESPACE) { + return new QName(attributeName, adlcpNamespace); + }else { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } + } + } + + QName adlseq(String attributeName, Namespace elementNamespace) { + if (Objects.equals(elementNamespace, adlseqNamespace)) { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } else { + if(CHECK_NAMESPACE) { + return new QName(attributeName, adlseqNamespace); + }else { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } + } + } + +// QName adlnav(String attributeName, Namespace elementNamespace) { +// if (Objects.equals(elementNamespace, adlnavNamespace)) { +// return new QName(attributeName, Namespace.NO_NAMESPACE); +// } else { +// if(CHECK_NAMESPACE) { +// return new QName(attributeName, adlnavNamespace); +// }else { +// return new QName(attributeName, Namespace.NO_NAMESPACE); +// } +// } +// } + + QName imsss(String attributeName, Namespace elementNamespace) { + if (Objects.equals(elementNamespace, imsssNamespace)) { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } else { + if(CHECK_NAMESPACE) { + return new QName(attributeName, imsssNamespace); + }else { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } + } + } + + QName lom(String attributeName, Namespace elementNamespace) { + if (Objects.equals(elementNamespace, lomNamespace)) { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } else { + if(CHECK_NAMESPACE) { + return new QName(attributeName, lomNamespace); + }else { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } + } + } + + QName xml(String attributeName, Namespace elementNamespace) { + if (Objects.equals(elementNamespace, xmlNamespace)) { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } else { + if(CHECK_NAMESPACE) { + return new QName(attributeName, xmlNamespace); + }else { + return new QName(attributeName, Namespace.NO_NAMESPACE); + } + } + } + } + +// public static void main(String[] args) { +// new ContentPackageGenerator().generateContentPackageFromFile("learningserver-scorm/scorm-test-pkg"); +// } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ContentPackageValidator.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ContentPackageValidator.java new file mode 100644 index 00000000..cf5b203c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ContentPackageValidator.java @@ -0,0 +1,2420 @@ +package com.xboe.module.scorm.cam.load; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import lombok.extern.slf4j.Slf4j; + +import com.xboe.module.scorm.cam.model.AdlseqMapInfo; +import com.xboe.module.scorm.cam.model.AdlseqObjective; +import com.xboe.module.scorm.cam.model.AdlseqObjectives; +import com.xboe.module.scorm.cam.model.Annotation; +import com.xboe.module.scorm.cam.model.Classification; +import com.xboe.module.scorm.cam.model.CompletionThreshold; +import com.xboe.module.scorm.cam.model.ConditionRule; +import com.xboe.module.scorm.cam.model.Content; +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.Contribute; +import com.xboe.module.scorm.cam.model.Data; +import com.xboe.module.scorm.cam.model.DateTime; +import com.xboe.module.scorm.cam.model.Dependency; +import com.xboe.module.scorm.cam.model.Duration; +import com.xboe.module.scorm.cam.model.Educational; +import com.xboe.module.scorm.cam.model.File; +import com.xboe.module.scorm.cam.model.General; +import com.xboe.module.scorm.cam.model.HideLMSUI; +import com.xboe.module.scorm.cam.model.Item; +import com.xboe.module.scorm.cam.model.LOM; +import com.xboe.module.scorm.cam.model.LanguageString; +import com.xboe.module.scorm.cam.model.LanguageStrings; +import com.xboe.module.scorm.cam.model.LifeCycle; +import com.xboe.module.scorm.cam.model.LimitConditions; +import com.xboe.module.scorm.cam.model.Manifest; +import com.xboe.module.scorm.cam.model.ManifestMetadata; +import com.xboe.module.scorm.cam.model.Map; +import com.xboe.module.scorm.cam.model.MapInfo; +import com.xboe.module.scorm.cam.model.MetaMetadata; +import com.xboe.module.scorm.cam.model.Metadata; +import com.xboe.module.scorm.cam.model.Objective; +import com.xboe.module.scorm.cam.model.Objectives; +import com.xboe.module.scorm.cam.model.OrComposite; +import com.xboe.module.scorm.cam.model.Organization; +import com.xboe.module.scorm.cam.model.Organizations; +import com.xboe.module.scorm.cam.model.Presentation; +import com.xboe.module.scorm.cam.model.RandomizationControls; +import com.xboe.module.scorm.cam.model.Relation; +import com.xboe.module.scorm.cam.model.Requirement; +import com.xboe.module.scorm.cam.model.Resource; +import com.xboe.module.scorm.cam.model.Resources; +import com.xboe.module.scorm.cam.model.Rights; +import com.xboe.module.scorm.cam.model.RollupCondition; +import com.xboe.module.scorm.cam.model.RollupConditions; +import com.xboe.module.scorm.cam.model.RollupConsiderations; +import com.xboe.module.scorm.cam.model.RollupRule; +import com.xboe.module.scorm.cam.model.RollupRules; +import com.xboe.module.scorm.cam.model.RuleCondition; +import com.xboe.module.scorm.cam.model.RuleConditions; +import com.xboe.module.scorm.cam.model.Sequencing; +import com.xboe.module.scorm.cam.model.SequencingCollection; +import com.xboe.module.scorm.cam.model.SequencingRules; +import com.xboe.module.scorm.cam.model.Taxon; +import com.xboe.module.scorm.cam.model.TaxonPath; +import com.xboe.module.scorm.cam.model.Technical; +import com.xboe.module.scorm.cam.model.Vocabulary; +import com.xboe.module.scorm.cam.model.datatype.Token; +import com.xboe.module.scorm.cam.model.datatype.VCard; +import com.xboe.module.scorm.cam.model.util.CPUtils; +import com.xboe.module.scorm.common.CommonUtils; + +@Slf4j +public class ContentPackageValidator { + + private static final String[] VT_TIME_LIMIT_ACTION + = {"exist,message", "exit,no message", "continue,message", "continue,no message"}; + private static final String[] VT_SCORM_TYPE + = {"sco", "asset"}; + private static final String[] VT_GENERAL_STRUCTURE + = {"atomic", "collection", "networked", "hierarchical", "linear"}; + private static final String[] VT_GENERAL_AGGREGATION_LEVEL + = {"1", "2", "3", "4"}; + private static final String[] VT_LIFE_CYCLE_STATUS + = {"draft", "final", "revised", "unavailable"}; + private static final String[] VT_LIFE_CYCLE_CONTRIBUTE_ROLE + = {"author", "publisher", "unknown", "initiator", "terminator", "validator", "editor", "graphical designer", + "technical implementer", "content provider", "technical validator", "educational validator", "script write", + "instructional designer", "subject matter expert"}; + private static final String[] VT_META_METADATA_CONTRIBUTE_ROLE + = {"creator", "validator"}; + private static final String[] VT_OR_COMPOSITE_TYPE + = {"operating system", "browser"}; + private static final String[] VT_OR_COMPOSITE_NAME_OS + = {"pc-dos", "ms-windows", "macos", "unix", "multi-os", "none"}; + private static final String[] VT_OR_COMPOSITE_NAME_B + = {"any", "netscape communicator", "ms-internet explorer", "opera", "amaya"}; + private static final String[] VT_EDUCATIONAL_INTERACTIVITY_TYPE + = {"active", "expositive", "mixed"}; + private static final String[] VT_EDUCATIONAL_LEARNING_RESOURCE_TYPE + = {"exercise", "simulation", "questionnaire", "diagram", "figure", "graph", "index", "slide", "table", + "narrative text", "exam", "experiment", "problem statement", "self assessment", "lecture"}; + private static final String[] VT_EDUCATIONAL_INTERACTIVITY_LEVEL + = {"very low", "low", "medium", "high", "very high"}; + private static final String[] VT_EDUCATIONAL_SEMANTIC_DENSITY + = {"very low", "low", "medium", "hight", "very high"}; + private static final String[] VT_EDUCATIONAL_INTENDED_END_USER_ROLE + = {"teacher", "author", "learner", "manager"}; + private static final String[] VT_EDUCATIONAL_CONTEXT + = {"higher education", "training", "other"}; + private static final String[] VT_EDUCATIONAL_DIFFICULTY + = {"very easy", "easy", "medium" ,"difficult", "very difficulty"}; + private static final String[] VT_RIGHTS_COST + = {"yes", "no"}; + private static final String[] VT_RIGHTS_COPYRIGHT_AND_OTHER_RESTRICTIONS + = {"yes", "no"}; + private static final String[] VT_RELATION_KIND + = {"ispartof", "haspart", "isversionof", "hasversion", "isformatof", "hasformat", "references", + "isreferencedby", "isbasedon", "isbasisfor", "requires", "isrequiredby"}; + private static final String[] VT_CLASSIFICATION_PURPOSE + = {"discipline", "idea", "prerequisite", "educational objective", "accessibility restrictions", + "educational level", "skill level", "security level", "competency"}; + private static final String[] VT_SEQUENCING_RULES_CONDITION_COMBINATION + = {"all", "any"}; + private static final String[] VT_SEQUENCING_RULES_RULE_CONDITION_OPERATOR + = {"not", "noOp"}; + private static final String[] VT_SEQUENCING_RULES_RULE_CONDITION_CONDITION + = {"satisfied", "objectiveStatusKnown", "objectiveMeasureKnown", "objectiveMeasureGreaterThan", + "objectiveMeasureLessThan", "completed", "activityProgressKnown", "attempted", "attemptLimitExceeded", + "timeLimitExceeded", "outsideAvailableTimeRange", "always"}; + private static final String[] VT_SEQUENCING_RULES_RULE_ACTION_PRE + = {"skip", "disabled", "hiddenFromChoice", "stopForwardTraversal"}; + private static final String[] VT_SEQUENCING_RULES_RULE_ACTION_POST + = {"exitParent", "exitAll", "retry", "retryAll", "continue", "previous"}; + private static final String[] VT_SEQUENCING_RULES_RULE_ACTION_EXIT + = {"exit"}; + private static final String[] VT_SEQUENCING_ROLLUP_RULE_CHILD_ACTIVITY_SET + = {"all", "any", "none", "atLeastCount", "atLeastPercent"}; + private static final String[] VT_SEQUENCING_ROLLUP_CONDITIONS_CONDITION_COMBINATION + = {"all", "any"}; + private static final String[] VT_SEQUENCING_ROLLUP_CONDITION_OPERATOR + = {"not", "noOp"}; + private static final String[] VT_SEQUENCING_ROLLUP_CONDITION_CONDITION + = {"satisfied", "objectiveStatusKnown", "objectiveMeasureKnown", "completed", "activityProgressKnown", + "attempted", "attemptLimitExceeded", "timeLimitExceeded", "outsideAvailableTimeRange"}; + private static final String[] VT_SEQUENCING_ROLLUP_ACTION + = {"satisfied", "notSatisfied", "completed", "incomplete"}; + private static final String[] VT_SEQUENCING_RANDOMIZATION_TIMING + = {"never", "once", "onEachNewAttempt"}; + private static final String[] VT_SEQUENCING_SELECTION_TIMING + = {"never", "once", "onEachNewAttempt"}; + private static final String[] VT_SEQUENCING_ROLLUP_CONSIDERATIONS + = {"always", "ifAttempted", "ifNotSkipped", "ifNotSuspended"}; + private static final String[] VT_PRESENTATION_HIDE_LMS_UI + = {"previous", "continue", "exit", "exitAll", "abandon", "abandonAll", "suspendAll"}; + + private boolean isAssert; + private java.util.Map> errors; + + public ContentPackageValidator() { + this(true); + } + + public ContentPackageValidator(boolean isAssert) { + this.isAssert = isAssert; + this.errors = new HashMap<>(); + } + + public boolean validate(ContentPackage contentPackage, String pkgSaveDir) { + this.errors.clear(); + if (contentPackage == null) { + recordError("ContentPackage", "cannot be null"); + return false; + } + + boolean result; + + // independent validation + result = validateManifest(contentPackage.getManifest()); + if (!result) { + return false; + } + + // dependent validation + result = independentValidate(contentPackage.getManifest()); + + result &= validateContent(contentPackage.getContent(), pkgSaveDir); + + return result; + } + + private boolean validateContent(Content content, String pkgSaveDir) { + boolean result = true; + boolean flag; + + for (String filePath : content.getPhysicalFilePathList()) { + result &= (flag = new java.io.File(pkgSaveDir + filePath).exists()); + if (!flag) { + recordError("physical file", "not found: " + filePath); + } + } + + return result; + } + + private boolean independentValidate(Manifest manifest) { + boolean result = true; + boolean flag; + + Set idSet = new HashSet<>(); + Set organizationIdSet = new HashSet<>(); + Set resourceIdSet = new HashSet<>(); + int count = 0; + + idSet.add(manifest.getIdentifier().getValue()); + count++; + + // #1: unique id within manifest + for (Organization organization : manifest.getOrganizations().getOrganizationList()) { + idSet.add(organization.getIdentifier().getValue()); + organizationIdSet.add(organization.getIdentifier().getValue()); + count++; + result &= (flag = idSet.size() == count); + if (!flag) { + recordError("...identifier", + "must be unique in the manifest: " + organization.getIdentifier().getValue()); + count--; + if (isAssert) { + return false; + } + } + for (Item item : organization.getItemList()) { + idSet.add(item.getIdentifier().getValue()); + count++; + result &= (flag = idSet.size() == count); + if (!flag) { + recordError("...[.].identifier", + "must be unique in the manifest: " + item.getIdentifier().getValue()); + count--; + if (isAssert) { + return false; + } + } + } + } + + for (Resource resource : manifest.getResources().getResourceList()) { + idSet.add(resource.getIdentifier().getValue()); + resourceIdSet.add(resource.getIdentifier().getValue()); + count++; + result &= (flag = idSet.size() == count); + if (!flag) { + recordError("...identifier", + "must be unique in the manifest: " + resource.getIdentifier().getValue()); + count--; + if (isAssert) { + return false; + } + } + } + + if (manifest.getSequencingCollection() != null) { + for (Sequencing sequencing : manifest.getSequencingCollection().getSequencingList()) { + idSet.add(sequencing.getId().getValue()); + count++; + result &= (flag = idSet.size() == count); + if (!flag) { + recordError("...ID", + "must be unique in the manifest: " + sequencing.getId().getValue()); + count--; + if (isAssert) { + return false; + } + } + } + } + + // #2: reference id + result &= (flag = organizationIdSet.contains(manifest.getOrganizations().getDefaultOrganizationID().getValue())); + if (!flag) { + recordError("..default", + "must reference an identifier attribute of an : " + + manifest.getOrganizations().getDefaultOrganizationID().getValue()); + if (isAssert) { + return false; + } + } + + Set referencedResourceIdSet = new HashSet<>(); + List leafItemList = new LinkedList<>(); + for (Organization organization : manifest.getOrganizations().getOrganizationList()) { + for (Item item : organization.getItemList()) { + result &= (flag = validateItemIDRef(item, resourceIdSet, referencedResourceIdSet, leafItemList)); + if (!flag && isAssert) { + return false; + } + } + } + + for (Resource resource : manifest.getResources().getResourceList()) { + for (Dependency dependency : resource.getDependencyList()) { + result &= (flag = resourceIdSet.contains(dependency.getIdentifierref())); + if (!flag) { + recordError("....identifierref", + "must reference an identifier attribute of an :" + dependency.getIdentifierref()); + if (isAssert) { + return false; + } + } + } + } + + for (Organization organization : manifest.getOrganizations().getOrganizationList()) { + if (organization.getSequencing() != null && !ModelUtils.isIDRefEmpty(organization.getSequencing().getIdRef())) { + result &= (flag = organizationIdSet.contains(organization.getSequencing().getIdRef().getValue())); + if (!flag) { + recordError("..[.[.]]..IDRef", + "must reference an ID attribute of an within : " + + organization.getSequencing().getIdRef().getValue()); + } + } + } + + // #3: referenced by leaf + Set referencedScoResourceIdSet = new HashSet<>(); + for (Resource resource : manifest.getResources().getResourceList()) { + if (referencedResourceIdSet.contains(resource.getIdentifier().getValue())) { + result &= (flag = StringUtils.equals(resource.getType(), "webcontent")); + if (!flag) { + recordError("...type", "" + + "the type attribute of the referenced by a leaf must be \"webcontent\""); + if (isAssert) { + return false; + } + } + result &= (flag = StringUtils.isNotBlank(resource.getHref())); + if (!flag) { + recordError("...type", "" + + "the href attribute of the referenced by a leaf is required"); + if (isAssert) { + return false; + } + } + if (StringUtils.equals(resource.getScormType(), "sco")) { + referencedScoResourceIdSet.add(resource.getIdentifier().getValue()); + } + } + } + + // #4: element only appear as a child of a leaf element that references a SCO resource + for (Item item : leafItemList) { + if (item.getData() != null) { + result &= (flag = referencedScoResourceIdSet.contains(item.getIdentifierref())); + if (!flag) { + recordError("..[.]..", + "only appear as a child of a leaf element that references a SCO resource"); + if (isAssert) { + return false; + } + } + } + } + + // TODO continue objective and mapInfo + // #5: objective unique within an activity. + for (Organization organization : manifest.getOrganizations().getOrganizationList()) { + if (organization.getSequencing() != null) { + result &= (flag = validateObjectiveID(organization.getSequencing(), manifest.getSequencingCollection())); + if (!flag && isAssert) { + return false; + } + } + for (Item item : organization.getItemList()) { + result &= (flag = validateObjectiveID(item, manifest.getSequencingCollection())); + if (!flag && isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateObjectiveID(Item item, SequencingCollection sequencingCollection) { + boolean result = true; + boolean flag; + if (item.getSequencing() != null) { + result = (flag = validateObjectiveID(item.getSequencing(), sequencingCollection)); + if (!flag && isAssert) { + return false; + } + } + for (Item child : item.getItemList()) { + if (child.getSequencing() != null) { + result &= (flag = validateObjectiveID(child.getSequencing(), sequencingCollection)); + if (!flag && isAssert) { + return false; + } + } + } + return result; + } + + private boolean validateObjectiveID(Sequencing sequencing, SequencingCollection sequencingCollection) { + List objectiveList = new LinkedList<>(); + List adlseqObjectiveList = new LinkedList<>(); + List ruleConditionList = new LinkedList<>(); + if (sequencing.getObjectives() != null) { + if (!ModelUtils.isAnyUriEmpty(sequencing.getObjectives().getPrimaryObjective().getObjectiveID())) { + objectiveList.add(sequencing.getObjectives().getPrimaryObjective()); + } + objectiveList.addAll(sequencing.getObjectives().getObjectiveList()); + } + if (sequencing.getAdlseqObjectives() != null) { + adlseqObjectiveList.addAll(sequencing.getAdlseqObjectives().getObjectiveList()); + } + if (!ModelUtils.isIDRefEmpty(sequencing.getIdRef())) { + Sequencing refSeq = CPUtils.findSequencingByID(sequencingCollection, sequencing.getIdRef().getValue()); + if (refSeq != null) { + if (refSeq.getObjectives() != null) { + objectiveList.add(refSeq.getObjectives().getPrimaryObjective()); + objectiveList.addAll(refSeq.getObjectives().getObjectiveList()); + } + if (refSeq.getAdlseqObjectives() != null) { + adlseqObjectiveList.addAll(refSeq.getAdlseqObjectives().getObjectiveList()); + } + } + } + if (sequencing.getSequencingRules() != null) { + for (ConditionRule conditionRule : sequencing.getSequencingRules().getPreConditionRuleList()) { + ruleConditionList.addAll(conditionRule.getRuleConditions().getRuleConditionList()); + } + for (ConditionRule conditionRule : sequencing.getSequencingRules().getPostConditionRuleList()) { + ruleConditionList.addAll(conditionRule.getRuleConditions().getRuleConditionList()); + } + for (ConditionRule conditionRule : sequencing.getSequencingRules().getExitConditionRuleList()) { + ruleConditionList.addAll(conditionRule.getRuleConditions().getRuleConditionList()); + } + } + return validateObjectiveID(objectiveList, adlseqObjectiveList, ruleConditionList); + } + + private boolean validateObjectiveID(List objectiveList, List adlseqObjectiveList, + List ruleConditionList) { + boolean result; + boolean flag; + Set objectiveIDSet = new HashSet<>(); + for (Objective objective : objectiveList) { + objectiveIDSet.add(objective.getObjectiveID().getValue()); + } + result = (flag = objectiveIDSet.size() == objectiveList.size()); + if (!flag) { + recordError("..[[.].]." + + "...objectiveID", + "must be unique in an item(exclude ancestor item or organization, " + + "include reference sequencing in sequencing Collection)"); + if (isAssert) { + return false; + } + } + for (AdlseqObjective adlseqObjective : adlseqObjectiveList) { + result &= (flag = objectiveIDSet.contains(adlseqObjective.getObjectiveID().getValue())); + if (!flag) { + recordError("..[[.].]." + + "...objectiveID", + "must match one objectiveID of the IMS SS .objectiveID in the same item, " + + "(exclude ancestor item or organization, include reference sequencing in SequencingCollection)"); + if (isAssert) { + return false; + } + } + } + for (RuleCondition ruleCondition : ruleConditionList) { + if (StringUtils.isNotBlank(ruleCondition.getReferencedObjective())) { + result &= (flag = objectiveIDSet.contains(ruleCondition.getReferencedObjective())); + if (!flag) { + recordError("..[[.].]." + + "..(||" + + ")...referencedObjective", + "must be one objectiveID of the IMS SS .objectiveID in the same item, " + + "(exclude ancestor item or organization, include reference sequencing in SequencingCollection)"); + if (isAssert) { + return false; + } + } + } + } + return result; + } + + private boolean validateItemIDRef(Item item, Set resourceIdSet, + Set referencedResourceIdSet, List leafItemList) { + boolean result = true; + boolean flag; + + if (item.getItemList().isEmpty()) { + result = (flag = resourceIdSet.contains(item.getIdentifierref())); + if (!flag) { + recordError("..[.].", + "must reference an identifier attribute of an : " + item.getIdentifierref()); + if (isAssert) { + return false; + } + } else { + referencedResourceIdSet.add(item.getIdentifierref()); + leafItemList.add(item); + } + } else { + for (Item childItem : item.getItemList()) { + result &= (flag = validateItemIDRef(childItem, resourceIdSet, referencedResourceIdSet, leafItemList)); + if (!flag && isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateManifest(Manifest manifest) { + if (manifest == null) { + recordError("", "must exist 1 and only 1 time"); + return false; + } + + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isIDEmpty(manifest.getIdentifier())); + if (!flag) { + recordError(".identifier", "must exist"); + if (isAssert) { + return false; + } + } + // need to dv .identifier -> unique within the manifest element + + if (!ModelUtils.isAnyUriEmpty(manifest.getXmlBase())) { + result &= (flag = ModelUtils.isAnyUriFormatCorrect(manifest.getXmlBase())); + if (!flag) { + recordError(".xml:base", "value format incorrect"); + if (isAssert) { + return false; + } + } + } + + result &= (flag = validateManifestMetadata(manifest.getMetadata())); + if (!flag) { + if (isAssert) { + return false; + } + } + + result &= (flag = validateOrganizations(manifest.getOrganizations())); + if (!flag) { + if (isAssert) { + return false; + } + } + + result &= (flag = validateResources(manifest.getResources())); + if (!flag) { + if (isAssert) { + return false; + } + } + + if (manifest.getSequencingCollection() != null) { + result &= (flag = validateSequencingCollection(manifest.getSequencingCollection())); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateManifestMetadata(ManifestMetadata manifestMetadata) { + if (manifestMetadata == null) { + recordError(".", "must exist 1 and only 1 time"); + return false; + } + + boolean result; + boolean flag; + + result = (flag = StringUtils.isNotBlank(manifestMetadata.getSchema())); + if (!flag) { + recordError("..", "must exist 1 and only 1 time"); + if (isAssert) { + return false; + } + } else { + result = (flag = StringUtils.equals(manifestMetadata.getSchema(), "ADL SCORM")); + if (!flag) { + recordError("..", "value must be \"ADL SCORM\""); + if (isAssert) { + return false; + } + } + } + + result &= (flag = StringUtils.isNotBlank(manifestMetadata.getSchemaVersion())); + if (!flag) { + recordError("..", "must exist 1 and only 1 time"); + if (isAssert) { + return false; + } + } else { + result &= (flag = StringUtils.equals(manifestMetadata.getSchemaVersion(), "2004 4th Edition")); + if (!flag) { + recordError("..", "value must be \"2004 4th Edition\""); + if (isAssert) { + return false; + } + } + } + + result &= (flag = validateMetadata(manifestMetadata.getMetadata())); + if (!flag && isAssert) { + return false; + } + + return result; + } + + private boolean validateOrganizations(Organizations organizations) { + if (organizations == null) { + recordError(".", "must exist 1 and only 1 time"); + return false; + } + + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isIDRefEmpty(organizations.getDefaultOrganizationID())); + if (!flag) { + recordError("..default", "must exist"); + if (isAssert) { + return false; + } + } + // need to dv ..default -> must reference an identifier attribute of an + + for (Organization organization : organizations.getOrganizationList()) { + result &= (flag = validateOrganization(organization)); + if (!flag) { + if (!isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateOrganization(Organization organization) { + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isIDEmpty(organization.getIdentifier())); + if (!flag) { + recordError("...identifier", "must exist"); + if (isAssert) { + return false; + } + } + // need to dv ...identifier -> unique within the manifest file + + result &= (flag = StringUtils.isNotBlank(organization.getTitle())); + if (!flag) { + recordError("...", + "must exist 1 and only 1 time and value not empty"); + if (isAssert) { + return false; + } + } + + result &= (flag = !organization.getItemList().isEmpty()); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>", "must exist 1 or more times"); + if (isAssert) { + return false; + } + } + + for (Item item : organization.getItemList()) { + result &= (flag = validateItem(item)); + if (!flag) { + if (isAssert) { + return false; + } + } + } + + if (organization.getCompletionThreshold() != null) { + result &= (flag = validateCompletionThreshold(organization.getCompletionThreshold())); + if (!flag) { + if (isAssert) { + return false; + } + } + } + + result &= (flag = validateMetadata(organization.getMetadata())); + if (!flag && isAssert) { + return false; + } + + if (organization.getSequencing() != null) { + result &= (flag = validateSequencing(organization.getSequencing(), false)); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateItem(Item item) { + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isIDEmpty(item.getIdentifier())); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].identifier", "must exist"); + if (isAssert) { + return false; + } + } + // need to dv <manifest>.<organizations>.<organization>.<item>[.<item>].identifier -> unique within the manifest + + if (item.getItemList().isEmpty()) { // is a leaf <item> + result &= (flag = StringUtils.isNotBlank(item.getIdentifierref())); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].identifierref", + "a leaf <item> is required to reference a resource"); + if (isAssert) { + return false; + } + } + // need to dv <manifest>.<organizations>.<organization>.<item>[.<item>].identifierref -> reference to an identifier in resources section + // need to dv the resource must be type=webcontent,scormType=sco|asset,href is required + } else { // not a leaf <item> + result &= (flag = StringUtils.isBlank(item.getIdentifierref())); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].identifierref", + "only a leaf <item> can reference a resource"); + if (isAssert) { + return false; + } + } + } + + if (StringUtils.isNotBlank(item.getParameters())) { + result &= (flag = ModelUtils.isParametersFormatCorrect(item.getParameters())); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].parameters", + "invalid format defined in RFC 3986"); + if (isAssert) { + return false; + } + } + } + + result &= (flag = StringUtils.isNotBlank(item.getTitle())); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].<title>", + "must exist 1 and only 1 time"); + if (isAssert) { + return false; + } + } + + for (Item nestedItem : item.getItemList()) { + result &= (flag = validateItem(nestedItem)); + if (!flag & isAssert) { + return false; + } + } + + if (StringUtils.isNotBlank(item.getTimeLimitAction())) { + result &= (flag = ModelUtils.isLegalVocabulary(item.getTimeLimitAction(), VT_TIME_LIMIT_ACTION)); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].<adlcp:timeLimitAction>", + CommonUtils.format("value must be one of following tokens: {}", Arrays.toString(VT_TIME_LIMIT_ACTION))); + if (isAssert) { + return false; + } + } + } + + if (item.getCompletionThreshold() != null) { + result &= (flag = validateCompletionThreshold(item.getCompletionThreshold())); + if (!flag && isAssert) { + return false; + } + } + + // need to dv CAM-3-36 + if (item.getData() != null) { + result &= (flag == item.getItemList().isEmpty()); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].<adlcp:data>", + "only appear as a child of a leaf <item>"); + if (isAssert) { + return false; + } + } else { + result &= (flag = validateData(item.getData())); + if (!flag & isAssert) { + return false; + } + } + } + + result &= (flag = validateMetadata(item.getMetadata())); + if (!flag && isAssert) { + return false; + } + + if (item.getSequencing() != null) { + result &= (flag = validateSequencing(item.getSequencing(), false)); + if (!flag && isAssert) { + return false; + } + } + + if (item.getPresentation() != null) { + result &= (flag = item.getItemList().isEmpty()); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].<adlnav:presentation>", + "only appear as a child of a leaf <item> element"); + if (isAssert) { + return false; + } + } else { + result &= (flag = validatePresentation(item.getPresentation())); + if (!flag && isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateCompletionThreshold(CompletionThreshold completionThreshold) { + boolean result; + boolean flag; + + result = (flag = ModelUtils.isDecimalInRange(completionThreshold.getMinProgressMeasure(), + 0.0000, 1.0000, 4)); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>[.<item>[.<item>]].<adlcp:completionThreshold>.minProgressMeasure", + "must in range [0.0000, 1.0000], with a precisions to at least 4 significant digits"); + if (isAssert) { + return false; + } + } + + result &= (flag = ModelUtils.isDecimalInRange(completionThreshold.getProgressWeight(), + 0.0000, 1.0000, 4)); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>[.<item>[.<item>]].<adlcp:completionThreshold>.progressWeight", + "must in range [0.0000, 1.0000], with a precisions to at least 4 significant digits"); + if (isAssert) { + return false; + } + } + + return result; + } + + private boolean validateData(Data data) { + boolean result; + boolean flag; + + result = (flag = !data.getMapList().isEmpty()); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].<adlcp:data>.<adlcp:map>", + "must exist 1 or more times"); + if (isAssert) { + return false; + } + } + + for (Map map : data.getMapList()) { + result &= (flag = ModelUtils.isAnyUriFormatCorrect(map.getTargetID())); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].<adlcp:data>.<adlcp:map>.targetID", + "must exist and has correct format"); + if (isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateResources(Resources resources) { + if (resources == null) { + recordError("<manifest>.<resources>", "must exist 1 and only 1 time"); + return false; + } + + boolean result = true; + boolean flag; + + if (!ModelUtils.isAnyUriEmpty(resources.getXmlBase())) { + result = (flag = ModelUtils.isAnyUriFormatCorrect(resources.getXmlBase())); + if (!flag) { + recordError("<manifest>.<resources>.xml:base", "value format incorrect"); + if (isAssert) { + return false; + } + } + } + + for (Resource resource : resources.getResourceList()) { + result &= (flag = validateResource(resource)); + if (!flag & isAssert) { + return false; + } + } + + return result; + } + + private boolean validateResource(Resource resource) { + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isIDEmpty(resource.getIdentifier())); + if (!flag) { + recordError("<manifest>.<resources>.<resource>.identifier", "must exist"); + if (isAssert) { + return false; + } + } + // need to dv <manifest>.<resources>.<resource>.identifier -> unique within the scope of its containing manifest + + result &= (flag = StringUtils.isNotBlank(resource.getType())); + if (!flag) { + recordError("<manifest>.<resources>.<resource>.type", "must exist"); + if (isAssert) { + return false; + } + } + + if (!ModelUtils.isAnyUriEmpty(resource.getXmlBase())) { + result = (flag = ModelUtils.isAnyUriFormatCorrect(resource.getXmlBase())); + if (!flag) { + recordError("<manifest>.<resources>.<resource>.xml:base", "value format incorrect"); + if (isAssert) { + return false; + } + } + } + + result &= (flag = StringUtils.isNotBlank(resource.getScormType())); + if (!flag) { + recordError("<manifest>.<resources>.<resource>.adlcp:scormType", "must exist"); + if (isAssert) { + return false; + } + } else { + result &= (flag = ModelUtils.isLegalVocabulary(resource.getScormType(), VT_SCORM_TYPE)); + if (!flag) { + recordError("<manifest>.<resources>.<resource>.adlcp:scormType", CommonUtils.format( + "value must be one of the following tokens: {}", Arrays.toString(VT_SCORM_TYPE))); + if (isAssert) { + return false; + } + } + } + + for (File file : resource.getFileList()) { + result &= (flag = validateFile(file)); + if (!flag && isAssert) { + return false; + } + } + + for (Dependency dependency : resource.getDependencyList()) { + result &= (flag = StringUtils.isNotBlank(dependency.getIdentifierref())); + if (!flag) { + recordError("<manifest>.<resources>.<resource>.<dependency>.identifierref", "must exist"); + if (isAssert) { + return false; + } + } + // need to dv <manifest>.<resources>.<resource>.<dependency>.identifierref -> reference a <resource> + } + + result &= (flag = validateMetadata(resource.getMetadata())); + if (!flag && isAssert) { + return false; + } + + return result; + } + + private boolean validateFile(File file) { + boolean result; + boolean flag; + + result = (flag = StringUtils.isNotBlank(file.getHref())); + if (!flag) { + recordError("<manifest>.<resources>.<resource>.<file>.href", "must exist"); + if (isAssert) { + return false; + } + } + + result &= (flag = validateMetadata(file.getMetadata())); + if (!flag && isAssert) { + return false; + } + + return result; + } + + private boolean validateMetadata(Metadata metadata) { + if (metadata == null) { + return true; + } + + boolean result = true; + boolean flag; + + for (LOM lom : metadata.getLomList()) { + result &= (flag = validateLOM(lom, "<manifest>...<metadata>.<lom>")); + if (!flag && isAssert) { + return false; + } + } + + result &= (flag = !metadata.getLocationLomMap().containsValue(null)); + if (!flag) { + recordError("<manifest>...<metadata>.<adlcp:location>", "xml file referenced by \"location\" is not found"); + if (isAssert) { + return false; + } + } + + for (java.util.Map.Entry<String, LOM> entry : metadata.getLocationLomMap().entrySet()) { + result &= (flag = validateLOM(entry.getValue(), "<lom>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateLOM(LOM lom, String baseTag) { + boolean result = true; + boolean flag; + + if (lom.getGeneral() != null) { + result = (flag = validateGeneral(lom.getGeneral(), baseTag)); + if (!flag && isAssert) { + return false; + } + } + + if (lom.getLifeCycle() != null) { + result &= (flag = validateLifeCycle(lom.getLifeCycle(), baseTag)); + if (!flag && isAssert) { + return false; + } + } + + if (lom.getMetaMetadata() != null) { + result &= (flag = validateMetaMetadata(lom.getMetaMetadata(), baseTag)); + if (!flag && isAssert) { + return false; + } + } + + if (lom.getTechnical() != null) { + result &= (flag = validateTechnical(lom.getTechnical(), baseTag)); + if (!flag && isAssert) { + return false; + } + } + + for (Educational educational : lom.getEducationalList()) { + result &= (flag = validateEducational(educational, baseTag)); + if (!flag && isAssert) { + return false; + } + } + + if (lom.getRights() != null) { + result &= (flag = validateRights(lom.getRights(), baseTag)); + if (!flag && isAssert) { + return false; + } + } + + for (Relation relation : lom.getRelationList()) { + result &= (flag = validateRelation(relation, baseTag)); + if (!flag && isAssert) { + return false; + } + } + + for (Annotation annotation : lom.getAnnotationList()) { + result &= (flag = validateAnnotation(annotation, baseTag)); + if (!flag && isAssert) { + return false; + } + } + + for (Classification classification : lom.getClassificationList()) { + result &= (flag = validateClassification(classification, baseTag)); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateGeneral(General general, String baseTag) { + boolean result = true; + boolean flag; + + if (general.getTitle() != null) { + result = (flag = validateLanguageStrings(general.getTitle(), baseTag + ".<general>.<title>")); + if (!flag && isAssert) { + return false; + } + } + + for (String language : general.getLanguageList()) { + result &= (flag = ModelUtils.isLegalLanguage(language)); + if (!flag) { + recordError(baseTag + ".<general>.<language>", "invalid language code"); + if (isAssert) { + return false; + } + } + } + + for (LanguageStrings languageStrings : general.getDescriptionList()) { + result &= (flag = validateLanguageStrings(languageStrings, baseTag + ".<general>.<description>")); + if (!flag && isAssert) { + return false; + } + } + + for (LanguageStrings languageStrings : general.getKeywordList()) { + result &= (flag = validateLanguageStrings(languageStrings, baseTag + ".<general>.<keyword>")); + if (!flag && isAssert) { + return false; + } + } + + for (LanguageStrings languageStrings : general.getCoverageList()) { + result &= (flag = validateLanguageStrings(languageStrings, baseTag + ".<general>.<coverage>")); + if (!flag && isAssert) { + return false; + } + } + + if (general.getStructure() != null) { + result &= (flag = validateVocabulary(general.getStructure(), VT_GENERAL_STRUCTURE, + baseTag + ".<general>.<structure>")); + if (!flag && isAssert) { + return false; + } + } + + if (general.getAggregationLevel() != null) { + result &= (flag = validateVocabulary(general.getAggregationLevel(), VT_GENERAL_AGGREGATION_LEVEL, + baseTag + ".<general>.<aggregationLevel>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateLifeCycle(LifeCycle lifeCycle, String baseTag) { + boolean result = true; + boolean flag; + + if (lifeCycle.getVersion() != null) { + result = (flag = validateLanguageStrings(lifeCycle.getVersion(), baseTag + ".<lifeCycle>.<version>")); + if (!flag && isAssert) { + return false; + } + } + + if (lifeCycle.getStatus() != null) { + result &= (flag = validateVocabulary(lifeCycle.getStatus(), VT_LIFE_CYCLE_STATUS, + baseTag + ".<lifeCycle>.<status>")); + if (!flag && isAssert) { + return false; + } + } + + for (Contribute contribute : lifeCycle.getContributeList()) { + result &= (flag = validateContribute(contribute, VT_LIFE_CYCLE_CONTRIBUTE_ROLE, + baseTag + ".<lifeCycle>.<contribute>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateMetaMetadata(MetaMetadata metaMetadata, String baseTag) { + boolean result = true; + boolean flag; + + for (Contribute contribute : metaMetadata.getContributeList()) { + result &= (flag = validateContribute(contribute, VT_META_METADATA_CONTRIBUTE_ROLE, + baseTag + ".<metaMetadata>.<contribute>")); + if (!flag && isAssert) { + return false; + } + } + + if (StringUtils.isNotBlank(metaMetadata.getLanguage())) { + result &= (flag = ModelUtils.isLegalLanguage(metaMetadata.getLanguage())); + if (!flag) { + recordError(baseTag + ".<metaMetadata>.<language>", "value invalid language code"); + if (isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateTechnical(Technical technical, String baseTag) { + boolean result = true; + boolean flag; + + if (technical.getSize() != null) { + result = (flag = technical.getSize().matches("^[0-9]\\d*$")); + if (!flag) { + recordError(baseTag + ".<technical>.<size>", "must be a non-negative-integer"); + if (isAssert) { + return false; + } + } + } + + for (Requirement requirement : technical.getRequirementList()) { + result &= (flag = validateRequirement(requirement, baseTag + ".<technical>.<requirement>")); + if (!flag && isAssert) { + return false; + } + } + + if (technical.getInstallationRemarks() != null) { + result &= (flag = validateLanguageStrings(technical.getInstallationRemarks(), + baseTag + ".<technical>.<installationRemarks>")); + if (!flag && isAssert) { + return false; + } + } + + if (technical.getOtherPlatformRequirements() != null) { + result &= (flag = validateLanguageStrings(technical.getOtherPlatformRequirements(), + baseTag + ".<technical>.<otherPlatformRequirements>")); + if (!flag && isAssert) { + return false; + } + } + + if (technical.getDuration() != null) { + result &= (flag = validateDuration(technical.getDuration(), baseTag + ".<technical>.<duration>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateEducational(Educational educational, String baseTag) { + boolean result = true; + boolean flag; + + if (educational.getInteractivityType() != null) { + result = (flag = validateVocabulary(educational.getInteractivityType(), VT_EDUCATIONAL_INTERACTIVITY_TYPE, + baseTag + ".<educational>.<interactivityType>")); + if (!flag && isAssert) { + return false; + } + } + + for (Vocabulary vocabulary : educational.getLearningResourceTypeList()) { + result &= (flag = validateVocabulary(vocabulary, VT_EDUCATIONAL_LEARNING_RESOURCE_TYPE, + baseTag + ".<educational>.<learningResourceType>")); + if (!flag && isAssert) { + return false; + } + } + + if (educational.getInteractivityLevel() != null) { + result &= (flag = validateVocabulary(educational.getInteractivityType(), VT_EDUCATIONAL_INTERACTIVITY_LEVEL, + baseTag + ".<educational>.<interactivityLevel>")); + if (!flag && isAssert) { + return false; + } + } + + if (educational.getSemanticDensity() != null) { + result &= (flag = validateVocabulary(educational.getInteractivityType(), VT_EDUCATIONAL_SEMANTIC_DENSITY, + baseTag + ".<educational>.<semanticDensity>")); + if (!flag && isAssert) { + return false; + } + } + + for (Vocabulary vocabulary : educational.getIntendedEndUserRoleList()) { + result &= (flag = validateVocabulary(vocabulary, VT_EDUCATIONAL_INTENDED_END_USER_ROLE, + baseTag + ".<educational>.<intendedEndUserRole>")); + if (!flag && isAssert) { + return false; + } + } + + for (Vocabulary vocabulary : educational.getContextList()) { + result &= (flag = validateVocabulary(vocabulary, VT_EDUCATIONAL_CONTEXT, + baseTag + ".<educational>.<context>")); + if (!flag && isAssert) { + return false; + } + } + + for (LanguageStrings languageStrings : educational.getTypicalAgeRangeList()) { + result &= (flag = validateLanguageStrings(languageStrings, + baseTag + ".<educational>.<typicalAgeRange>")); + if (!flag && isAssert) { + return false; + } + } + + if (educational.getDifficulty() != null) { + result &= (flag = validateVocabulary(educational.getDifficulty(), VT_EDUCATIONAL_DIFFICULTY, + baseTag + ".<educational>.<difficulty>")); + if (!flag && isAssert) { + return false; + } + } + + if (educational.getTypicalLearningTime() != null) { + result &= (flag = validateDuration(educational.getTypicalLearningTime(), + baseTag + ".<educational>.<typicalLearningTime>")); + if (!flag && isAssert) { + return false; + } + } + + for (LanguageStrings languageStrings : educational.getDescriptionList()) { + result &= (flag = validateLanguageStrings(languageStrings, baseTag + ".<educational>.<description>")); + if (!flag && isAssert) { + return false; + } + } + + for (String language : educational.getLanguageList()) { + result &= (flag = ModelUtils.isLegalLanguage(language)); + if (!flag) { + recordError(baseTag + ".<educational>.<language>", "value invalid language code"); + if (isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateRights(Rights rights, String baseTag) { + boolean result = true; + boolean flag; + + if (rights.getCost() != null) { + result = (flag = validateVocabulary(rights.getCost(), VT_RIGHTS_COST, baseTag + ".<rights>.<cost>")); + if (!flag && isAssert) { + return false; + } + } + + if (rights.getCopyrightAndOtherRestrictions() != null) { + result &= (flag = validateVocabulary(rights.getCost(), VT_RIGHTS_COPYRIGHT_AND_OTHER_RESTRICTIONS, + baseTag + ".<rights>.<copyrightAndOtherRestrictions>")); + if (!flag && isAssert) { + return false; + } + } + + + if (rights.getDescription() != null) { + result &= (flag = validateLanguageStrings(rights.getDescription(), baseTag + ".<rights>.<description>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateRelation(Relation relation, String baseTag) { + boolean result = true; + boolean flag; + + if (relation.getKind() != null) { + result = (flag = validateVocabulary(relation.getKind(), VT_RELATION_KIND, + baseTag + ".<relation>.<kind>")); + if (!flag && isAssert) { + return false; + } + } + + for (LanguageStrings languageStrings : relation.getResource().getDescriptionList()) { + result &= (flag = validateLanguageStrings(languageStrings, baseTag + ".<relation>.<description>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateAnnotation(Annotation annotation, String baseTag) { + boolean result = true; + boolean flag; + + if (ModelUtils.isVCardEmpty(annotation.getEntity())) { + result = (flag = ModelUtils.isLegalVCard(annotation.getEntity())); + if (!flag) { + recordError(baseTag + ".<annotation>.<entity>", "value format incorrect"); + if (isAssert) { + return false; + } + } + } + + if (annotation.getDate() != null) { + result &= (flag = validateDateTime(annotation.getDate(), baseTag + ".<annotation>.<date>")); + if (!flag && isAssert) { + return false; + } + } + + if (annotation.getDescription() != null) { + result &= (flag = validateLanguageStrings(annotation.getDescription(), + baseTag + ".<annotation>.<description>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateClassification(Classification classification, String baseTag) { + boolean result = true; + boolean flag; + + if (classification.getPurpose() != null) { + result = (flag = validateVocabulary(classification.getPurpose(), VT_CLASSIFICATION_PURPOSE, + baseTag + ".<classification>.<purpose>")); + if (!flag && isAssert) { + return false; + } + } + + for (TaxonPath taxonPath : classification.getTaxonPathList()) { + result &= (flag = validateTaxonPath(taxonPath, baseTag + ".<classification>.<taxonPath>")); + if (!flag && isAssert) { + return false; + } + } + + if (classification.getDescription() != null) { + result &= (flag = validateLanguageStrings(classification.getDescription(), + baseTag + ".<classification>.<description>")); + if (!flag && isAssert) { + return false; + } + } + + for (LanguageStrings languageStrings : classification.getKeywordList()) { + result &= (flag = validateLanguageStrings(languageStrings, baseTag + ".<classification>.<keyword>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateContribute(Contribute contribute, String[] roleVT, String baseTag) { + boolean result = true; + boolean flag; + + if (contribute.getRole() != null) { + result = (flag = validateVocabulary(contribute.getRole(), roleVT, baseTag + ".<role>")); + if (!flag && isAssert) { + return false; + } + } + + for (VCard vCard : contribute.getEntityList()) { + result &= (flag = ModelUtils.isLegalVCard(vCard)); + if (!flag) { + recordError(baseTag + ".<entity>", "invalid vCard format"); + if (isAssert) { + return false; + } + } + } + + if (contribute.getDate() != null) { + result &= (flag = validateDateTime(contribute.getDate(), baseTag + ".<date>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateRequirement(Requirement requirement, String baseTag) { + boolean result = true; + boolean flag; + + for (OrComposite orComposite : requirement.getOrCompositeList()) { + result &= (flag = validateOrComposite(orComposite, baseTag + ".<orComposite>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateOrComposite(OrComposite orComposite, String baseTag) { + boolean result = true; + boolean flag; + + if (orComposite.getType() != null) { + result = (flag = validateVocabulary(orComposite.getType(), VT_OR_COMPOSITE_TYPE, + baseTag + ".<type>")); + if (!flag && isAssert) { + return false; + } else if (orComposite.getName() != null) { + String[] vt = orComposite.getType().getValue().equals("browser") ? + VT_OR_COMPOSITE_NAME_B : VT_OR_COMPOSITE_NAME_OS; + result &= (flag = validateVocabulary(orComposite.getName(), vt, + CommonUtils.format(baseTag + ".<name>{type={}}", orComposite.getType().getValue()))); + if (!flag && isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateTaxonPath(TaxonPath taxonPath, String baseTag) { + boolean result = true; + boolean flag; + + if (taxonPath.getSource() != null) { + result = (flag = validateLanguageStrings(taxonPath.getSource(), baseTag + ".<source>")); + if (!flag && isAssert) { + return false; + } + } + + for (Taxon taxon : taxonPath.getTaxonList()) { + result &= (flag = validateTaxon(taxon, baseTag + ".<taxon>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateTaxon(Taxon taxon, String baseTag) { + boolean result = true; + boolean flag; + + if (taxon.getEntry() != null) { + result = (flag = validateLanguageStrings(taxon.getEntry(), baseTag + ".<entry>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateLanguageStrings(LanguageStrings languageStrings, String baseTag) { + boolean result = true; + boolean flag; + + for (LanguageString languageString : languageStrings.getLanguageStringList()) { + result &= (flag = ModelUtils.isLegalLanguage(languageString.getLanguage())); + if (!flag) { + recordError(baseTag + ".<string>.language", "invalid value"); + if (isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateVocabulary(Vocabulary vocabulary, String[] vt, String baseTag) { + boolean result; + boolean flag; + + result = (flag = StringUtils.equals("LOMv1.0", vocabulary.getSource())); + if (!flag) { + recordError(baseTag + ".<source>", "value must be LOMv1.0"); + if (isAssert) { + return false; + } + } + + result &= (flag = ModelUtils.isLegalVocabulary(vocabulary.getValue(), vt)); + if (!flag) { + recordError(baseTag + ".<value>", CommonUtils.format( + "value must be one of the following tokens:", Arrays.toString(vt))); + if (isAssert) { + return false; + } + } + + return result; + } + + private boolean validateDateTime(DateTime dateTime, String baseTag) { + boolean result = true; + boolean flag; + + if (StringUtils.isNotBlank(dateTime.getDateTime())) { + result = (flag = ModelUtils.isDateTimeFormatCorrect(dateTime.getDateTime())); + if (!flag) { + recordError(baseTag + ".<dateTime>", "invalid dateTime format"); + if (isAssert) { + return false; + } + } + } + + if (dateTime.getDescription() != null) { + result &= (flag = validateLanguageStrings(dateTime.getDescription(), baseTag + ".<description>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateDuration(Duration duration, String baseTag) { + boolean result = true; + boolean flag; + + if (StringUtils.isNotBlank(duration.getDuration())) { + result = (flag = ModelUtils.isDurationFormatCorrect(duration.getDuration())); + if (!flag) { + recordError(baseTag + ".<duration>", "invalid duration format"); + if (isAssert) { + return false; + } + } + } + + if (duration.getDescription() != null) { + result &= (flag = validateLanguageStrings(duration.getDescription(), baseTag + ".<description>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateSequencing(Sequencing sequencing, boolean isInSequencingCollection) { + boolean result; + boolean flag; + + String baseTag = isInSequencingCollection ? "<manifest>.<imsss:sequencingCollection>.<imsss:sequencing>" : + "<manifest>.<organizations>.<organization>[.<item>[.<item>]].<imsss:sequencing>"; + + if (isInSequencingCollection) { + result = (flag = !ModelUtils.isIDEmpty(sequencing.getId())); + if (!flag) { + recordError(baseTag + ".ID", "must exist"); + if (isAssert) { + return false; + } + } + // need to dv <manifest>.<imsss:sequencingCollection>.<imsss:sequencing>.ID -> unique in manifest + + result &= (flag = ModelUtils.isIDRefEmpty(sequencing.getIdRef())); + if (!flag) { + recordError(baseTag + ".IDRef", "cannot exist"); + if (isAssert) { + return false; + } + } + } else { + result = (flag = ModelUtils.isIDEmpty(sequencing.getId())); + if (!flag) { + recordError(baseTag + ".ID", "cannot exist"); + if (isAssert) { + return false; + } + } + // need to dv baseTag.IDRef -> reference sequencing id + } + + if (sequencing.getSequencingRules() != null) { + result &= (flag = validateSequencingRules(sequencing.getSequencingRules(), + baseTag + ".<imsss:sequencingRules>")); + if (!flag && isAssert) { + return false; + } + } + + if (sequencing.getLimitConditions() != null) { + result &= (flag = validateLimitConditions(sequencing.getLimitConditions(), + baseTag + ".<imsss:limitConditions>")); + if (!flag && isAssert) { + return false; + } + } + + if (sequencing.getRollupRules() != null) { + result &= (flag = validateRollupRules(sequencing.getRollupRules(), + baseTag + ".<imsss:rollupRules>")); + if (!flag && isAssert) { + return false; + } + } + + if (sequencing.getObjectives() != null) { + result &= (flag = validateObjectives(sequencing.getObjectives(), + baseTag + ".<imsss:objectives>")); + if (!flag && isAssert) { + return false; + } + } + + if (sequencing.getRandomizationControls() != null) { + result &= (flag = validateRandomizationControls(sequencing.getRandomizationControls(), + baseTag + ".<imsss:randomizationControls>")); + if (!flag && isAssert) { + return false; + } + } + + if (sequencing.getRollupConsiderations() != null) { + result &= (flag = validateRollupConsiderations(sequencing.getRollupConsiderations(), + baseTag + ".<adlseq:rollupConsiderations>")); + if (!flag && isAssert) { + return false; + } + } + + if (sequencing.getAdlseqObjectives() != null) { + result &= (flag = validateAdlseqObjectives(sequencing.getAdlseqObjectives(), + baseTag + ".<adlseq:objectives>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateSequencingRules(SequencingRules sequencingRules, String baseTag) { + boolean result = true; + boolean flag; + + for (ConditionRule conditionRule : sequencingRules.getPreConditionRuleList()) { + result &= (flag = validateConditionRule(conditionRule, VT_SEQUENCING_RULES_RULE_ACTION_PRE, + baseTag + ".<imsss:preConditionRule>")); + if (!flag && isAssert) { + return false; + } + } + + for (ConditionRule conditionRule : sequencingRules.getPostConditionRuleList()) { + result &= (flag = validateConditionRule(conditionRule, VT_SEQUENCING_RULES_RULE_ACTION_POST, + baseTag + ".<imsss:postConditionRule>")); + if (!flag && isAssert) { + return false; + } + } + + for (ConditionRule conditionRule : sequencingRules.getExitConditionRuleList()) { + result &= (flag = validateConditionRule(conditionRule, VT_SEQUENCING_RULES_RULE_ACTION_EXIT, + baseTag + ".<imsss:exitConditionRule>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateConditionRule(ConditionRule conditionRule, String[] actionVT, String baseTag) { + boolean result; + boolean flag; + + result = (flag = validateRuleConditions(conditionRule.getRuleConditions(), + baseTag + ".<imsss:ruleConditions>")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = conditionRule.getRuleAction() != null && conditionRule.getRuleAction().getAction() != null); + if (!flag) { + recordError(baseTag + ".<imsss:ruleAction>.action", "must exist 1 and only 1 time"); + if (isAssert) { + return false; + } + } else { + result &= (flag = validateToken(conditionRule.getRuleAction().getAction(), actionVT, + baseTag + ".<imsss:ruleAction>.action")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateRuleConditions(RuleConditions ruleConditions, String baseTag) { + if (ruleConditions == null) { + recordError(baseTag, "must exist 1 and only 1 time"); + return false; + } + boolean result; + boolean flag; + + result = (flag = validateToken(ruleConditions.getConditionCombination(), + VT_SEQUENCING_RULES_CONDITION_COMBINATION, baseTag + ".conditionCombination")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = !ruleConditions.getRuleConditionList().isEmpty()); + if (!flag) { + recordError(baseTag + ".<imsss:ruleCondition>", "must exist 1 or more times"); + if (isAssert) { + return false; + } + } + + for (RuleCondition ruleCondition : ruleConditions.getRuleConditionList()) { + result &= (flag = validateRuleCondition(ruleCondition, baseTag + ".<imsss:ruleCondition>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateRuleCondition(RuleCondition ruleCondition, String baseTag) { + boolean result = true; + boolean flag; + + // need to dv referencedObjective -> shall contain an objectiveID of either the <primaryObjective> or an <objective> element defined for the activity. + + if (!ModelUtils.isDecimalEmpty(ruleCondition.getMeasureThreshold())) { + result = (flag = ModelUtils.isDecimalInRange(ruleCondition.getMeasureThreshold(), + -1.0000, 1.0000, 4)); + if (!flag) { + recordError(baseTag + ".measureThreshold", + "must in range [-1.0000, 1.0000], with a precision of at least 4 decimal places"); + if (isAssert) { + return false; + } + } + } + + result &= (flag = validateToken(ruleCondition.getOperator(), VT_SEQUENCING_RULES_RULE_CONDITION_OPERATOR, + baseTag + ".operator")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = !ModelUtils.isTokenEmpty(ruleCondition.getCondition())); + if (!flag) { + recordError(baseTag + ".condition", "must exist"); + if (isAssert) { + return false; + } + } else { + result &= (flag = validateToken(ruleCondition.getCondition(), VT_SEQUENCING_RULES_RULE_CONDITION_CONDITION, + baseTag + ".condition")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateLimitConditions(LimitConditions limitConditions, String baseTag) { + boolean result = true; + boolean flag; + + if (ModelUtils.isNonNegativeIntegerEmpty(limitConditions.getAttemptLimit())) { + result = (flag = limitConditions.getAttemptLimit().getIntValue() >= 0); + if (!flag) { + recordError(baseTag + ".attemptLimit", "must be an non-negative integer"); + if (isAssert) { + return false; + } + } + } + + if (StringUtils.isNotBlank(limitConditions.getAttemptAbsoluteDurationLimit())) { + result &= (flag = ModelUtils.isDurationFormatCorrect(limitConditions.getAttemptAbsoluteDurationLimit())); + if (!flag) { + recordError(baseTag + ".attemptAbsoluteDurationLimit", "incorrect format for duration"); + if (isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateRollupRules(RollupRules rollupRules, String baseTag) { + boolean result = true; + boolean flag; + + if (ModelUtils.isDecimalEmpty(rollupRules.getObjectiveMeasureWeight())) { + result = (flag = ModelUtils.isDecimalInRange(rollupRules.getObjectiveMeasureWeight(), + 0.0000, 1.0000, 4)); + if (!flag) { + recordError(baseTag + ".objectiveMeasureWeight", + "must in range [0.0000, 1.0000], precision to at least 4 significant decimal places"); + if (isAssert) { + return false; + } + } + } + + for (RollupRule rollupRule : rollupRules.getRollupRuleList()) { + result &= (flag = validateRollupRule(rollupRule, baseTag + ".<imsss:rollupRule>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateRollupRule(RollupRule rollupRule, String baseTag) { + boolean result; + boolean flag; + + result = (flag = validateToken(rollupRule.getChildActivitySet(), + VT_SEQUENCING_ROLLUP_RULE_CHILD_ACTIVITY_SET, baseTag + ".childActivitySet")); + if (!flag && isAssert) { + return false; + } + + if (ModelUtils.isNonNegativeIntegerEmpty(rollupRule.getMinimumCount())) { + result &= (flag = rollupRule.getMinimumCount().getIntValue() >= 0); + if (!flag) { + recordError(baseTag + ".minimumCount", "must be an non-negative integer"); + if (isAssert) { + return false; + } + } + } + + if (ModelUtils.isDecimalEmpty(rollupRule.getMinimumPercent())) { + result &= (flag = ModelUtils.isDecimalInRange(rollupRule.getMinimumPercent(), + 0.0000, 1.0000, 4)); + if (!flag) { + recordError(baseTag + ".minimumPercent", + "must be in range [0.0000, 1.0000], precision to at least 4 significant decimal places"); + if (isAssert) { + return false; + } + } + } + + result &= (flag = validateRollupConditions(rollupRule.getRollupConditions(), + baseTag + ".<imsss:rollupConditions>")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = !ModelUtils.isTokenEmpty(rollupRule.getRollupAction())); + if (!flag) { + recordError(baseTag + ".<imsss:rollupAction>.action", "must exist 1 and only 1 time"); + if (isAssert) { + return false; + } + } else { + result &= (flag = validateToken(rollupRule.getRollupAction(), VT_SEQUENCING_ROLLUP_ACTION, + baseTag + ".<imsss:rollupAction>.action")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateRollupConditions(RollupConditions rollupConditions, String baseTag) { + if (rollupConditions == null) { + recordError(baseTag, "must exist 1 and only 1 time"); + return false; + } + boolean result; + boolean flag; + + result = (flag = validateToken(rollupConditions.getConditionCombination(), + VT_SEQUENCING_ROLLUP_CONDITIONS_CONDITION_COMBINATION, baseTag + ".conditionCombination")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = !rollupConditions.getRollupConditionList().isEmpty()); + if (!flag) { + recordError(baseTag + ".<imsss:rollupCondition>", "must exist 1 or more times"); + if (isAssert) { + return false; + } + } + + for (RollupCondition rollupCondition : rollupConditions.getRollupConditionList()) { + result &= (flag = validateRollupCondition(rollupCondition, baseTag + ".<imsss:rollupCondition>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateRollupCondition(RollupCondition rollupCondition, String baseTag) { + boolean result; + boolean flag; + + result = (flag = validateToken(rollupCondition.getOperator(), VT_SEQUENCING_ROLLUP_CONDITION_OPERATOR, + baseTag + ".operator")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = !ModelUtils.isTokenEmpty(rollupCondition.getCondition())); + if (!flag) { + recordError(baseTag + ".condition", "must exist"); + if (isAssert) { + return false; + } + } + + result &= (flag = validateToken(rollupCondition.getCondition(), VT_SEQUENCING_ROLLUP_CONDITION_CONDITION, + baseTag + ".condition")); + if (!flag && isAssert) { + return false; + } + + return result; + } + + private boolean validateObjectives(Objectives objectives, String baseTag) { + boolean result; + boolean flag; + + result = (flag = objectives.getPrimaryObjective() != null); + if (!flag) { + recordError(baseTag + ".<imsss:primaryObjective>", "must exist 1 and only 1 time"); + if (isAssert) { + return false; + } + } else { + result = (flag = validateObjective(objectives.getPrimaryObjective(), true, + baseTag + ".<imsss:primaryObjective>")); + if (!flag && isAssert) { + return false; + } + } + + for (Objective objective : objectives.getObjectiveList()) { + result &= (flag = validateObjective(objective, false, + baseTag + ".<imsss:objective>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateObjective(Objective objective, boolean isPrimary, String baseTag) { + boolean result; + boolean flag; + + result = (flag = (isPrimary && objective.getMapInfoList().isEmpty()) + || !ModelUtils.isAnyUriEmpty(objective.getObjectiveID())); + if (!flag) { + recordError(baseTag + ".objectiveID", "must exist"); + if (isAssert) { + return false; + } + } else if (!ModelUtils.isAnyUriEmpty(objective.getObjectiveID())) { + // need to dv .objectiveID -> unique within an activity + result = (flag = ModelUtils.isAnyUriFormatCorrect(objective.getObjectiveID())); + if (!flag) { + recordError(baseTag + ".objectiveID", "incorrect anyURI format"); + if (isAssert) { + return false; + } + } + } + + result &= (flag = ModelUtils.isDecimalInRange(objective.getMinNormalizedMeasure(), + -1.0, 1.0, 1)); + if (!flag) { + recordError(baseTag + ".<imssss:minNormalizedMeasure>", + "must in range [-1.00, 1.00], precision is 1"); + if (isAssert) { + return false; + } + } + + for (MapInfo mapInfo : objective.getMapInfoList()) { + result &= (flag = validateMapInfo(mapInfo, baseTag + ".<imsss:mapInfo>")); + if (!flag && isAssert) { + return false; + } + } + + // TODO need to dv CAM-5-29 + + return result; + } + + private boolean validateMapInfo(MapInfo mapInfo, String baseTag) { + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isAnyUriEmpty(mapInfo.getTargetObjectiveID())); + if (!flag) { + recordError(baseTag + ".targetObjectiveID", "must exist"); + if (isAssert) { + return false; + } + } else { + // TODO need to dv .targetObjectiveID -> referenced the global shared objective targeted for mapping +// saveId(baseTag + ".targetObjectiveID", mapInfo.getTargetObjectiveID().getValue()); + result = (flag = ModelUtils.isAnyUriFormatCorrect(mapInfo.getTargetObjectiveID())); + if (!flag) { + recordError(baseTag + ".targetObjectiveID", "incorrect anyURI format"); + if (isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateRandomizationControls(RandomizationControls randomizationControls, String baseTag) { + boolean result; + boolean flag; + + result = (flag = validateToken(randomizationControls.getRandomizationTiming(), + VT_SEQUENCING_RANDOMIZATION_TIMING, baseTag + ".randomizationTiming")); + if (!flag && isAssert) { + return false; + } + + if (!ModelUtils.isNonNegativeIntegerEmpty(randomizationControls.getSelectCount())) { + result &= (flag = randomizationControls.getSelectCount().getIntValue() >= 0); + if (!flag) { + recordError(baseTag + ".selectCount", "must be an non-negative integer"); + if (isAssert) { + return false; + } + } + } + + result &= (flag = validateToken(randomizationControls.getSelectionTiming(), + VT_SEQUENCING_SELECTION_TIMING, baseTag + ".selectionTiming")); + if (!flag && isAssert) { + return false; + } + + return result; + } + + private boolean validateRollupConsiderations(RollupConsiderations rollupConsiderations, String baseTag) { + boolean result; + boolean flag; + + result = (flag = validateToken(rollupConsiderations.getRequiredForSatisfied(), + VT_SEQUENCING_ROLLUP_CONSIDERATIONS, baseTag + ".requiredForSatisfied")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = validateToken(rollupConsiderations.getRequiredForNotSatisfied(), + VT_SEQUENCING_ROLLUP_CONSIDERATIONS, baseTag + ".requiredForNotSatisfied")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = validateToken(rollupConsiderations.getRequiredForCompleted(), + VT_SEQUENCING_ROLLUP_CONSIDERATIONS, baseTag + ".requiredForCompleted")); + if (!flag && isAssert) { + return false; + } + + result &= (flag = validateToken(rollupConsiderations.getRequiredForIncomplete(), + VT_SEQUENCING_ROLLUP_CONSIDERATIONS, baseTag + ".requiredForIncomplete")); + if (!flag && isAssert) { + return false; + } + + return result; + } + + private boolean validateAdlseqObjectives(AdlseqObjectives adlseqObjectives, String baseTag) { + boolean result; + boolean flag; + + result = (flag = !adlseqObjectives.getObjectiveList().isEmpty()); + if (!flag) { + recordError(baseTag + ".<adlseq:objective>", "must exist 1 or more times"); + if (isAssert) { + return false; + } + } + + for (AdlseqObjective adlseqObjective : adlseqObjectives.getObjectiveList()) { + result &= (flag = validateAdlseqObjective(adlseqObjective, baseTag + ".<adlseq:objective>")); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validateAdlseqObjective(AdlseqObjective adlseqObjective, String baseTag) { + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isAnyUriEmpty(adlseqObjective.getObjectiveID())); + if (!flag) { + recordError(baseTag + ".objectiveID", "must exist"); + if (isAssert) { + return false; + } + } else { + result = (flag = ModelUtils.isAnyUriFormatCorrect(adlseqObjective.getObjectiveID())); + if (!flag) { + recordError(baseTag + ".objectiveID", "incorrect anyURI format"); + if (isAssert) { + return false; + } + } + // need to dv .objectiveID -> match an objectiveID of imsss:objective within the same <sequencing> + } + + result &= (flag = !adlseqObjective.getMapInfoList().isEmpty()); + if (!flag) { + recordError(baseTag + ".<adlseq:mapInfo>", "must exist 1 or more times"); + if (isAssert) { + return false; + } + } + + for (AdlseqMapInfo adlseqMapInfo : adlseqObjective.getMapInfoList()) { + result &= (flag = validateAdlseqMapInfo(adlseqMapInfo, baseTag + ".<adlseq:mapInfo>")); + if (!flag && isAssert) { + return false; + } + } + + // TODO need to dv CAM-5-43 + + return result; + } + + private boolean validateAdlseqMapInfo(AdlseqMapInfo adlseqMapInfo, String baseTag) { + boolean result; + boolean flag; + + result = (flag = !ModelUtils.isAnyUriEmpty(adlseqMapInfo.getTargetObjectiveID())); + if (!flag) { + recordError(baseTag + ".targetObjectiveID", "must exist"); + if (isAssert) { + return false; + } + } else { + // TODO need to dv .targetObjectiveID -> referenced the global shared objective targeted for mapping +// saveId(baseTag + ".targetObjectiveID", adlseqMapInfo.getTargetObjectiveID().getValue()); + result = (flag = ModelUtils.isAnyUriFormatCorrect(adlseqMapInfo.getTargetObjectiveID())); + if (!flag) { + recordError(baseTag + ".targetObjectiveID", "anyURI format incorrect"); + if (isAssert) { + return false; + } + } + } + + return result; + } + + private boolean validateSequencingCollection(SequencingCollection sequencingCollection) { + boolean result; + boolean flag; + + result = (flag = !sequencingCollection.getSequencingList().isEmpty()); + if (!flag) { + recordError("<manifest>.<imsss:sequencingCollection>.<imsss:sequencing>", "must exist 1 or more times"); + if (isAssert) { + return false; + } + } + + for (Sequencing sequencing : sequencingCollection.getSequencingList()) { + result &= (flag = validateSequencing(sequencing, true)); + if (!flag && isAssert) { + return false; + } + } + + return result; + } + + private boolean validatePresentation(Presentation presentation) { + boolean result = true; + boolean flag; + + if (presentation.getNavigationInterface() != null) { + for (HideLMSUI hideLMSUI : presentation.getNavigationInterface().getHideLMSUIList()) { + result &= (flag = ModelUtils.isLegalVocabulary(hideLMSUI.getValue(), VT_PRESENTATION_HIDE_LMS_UI)); + if (!flag) { + recordError("<manifest>.<organizations>.<organization>.<item>[.<item>].<adlnav:presentation>", + "must be one of the following tokens: " + Arrays.toString(VT_PRESENTATION_HIDE_LMS_UI)); + if (isAssert) { + return false; + } + } + } + } + + return result; + } + + private boolean validateToken(Token token, String[] vt, String tag) { + boolean result = ModelUtils.isLegalToken(token, vt); + if (!result) { + recordError(tag, CommonUtils.format("must be one of the following tokens: {}", Arrays.toString(vt))); + } + return result; + } + + private void recordError(String tag, String errorMsg) { + String msg = CommonUtils.format("{} -> {}.", tag, errorMsg); + errors.computeIfAbsent(tag, k -> new LinkedList<>()).add(msg); + log.error("record error: {}", msg); + } + + public boolean isAssert() { + return isAssert; + } + + public void setAssert(boolean anAssert) { + isAssert = anAssert; + } + + public java.util.Map<String, List<String>> getErrors() { + java.util.Map<String, List<String>> res = new HashMap<>(); + errors.forEach((k, v) -> res.put(k, Collections.unmodifiableList(v))); + return Collections.unmodifiableMap(res); + } + +// public static void main(String[] args) { +// String[] table = {"satisfied", "objectiveStatusKnown", "objectiveMeasureKnown", "objectiveMeasureGreaterThan", +// "objectiveMeasureLessThan", "completed", "activityProgressKnown", "attempted", "attemptLimitExceeded", +// "timeLimitExceeded", "outsideAvailableTimeRange", "always"}; +// System.out.println(Arrays.binarySearch(table, "completed") >= 0); +// System.out.println(Arrays.binarySearch(table, "activityProgressKnown") >= 0); +// } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/FileUtils.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/FileUtils.java new file mode 100644 index 00000000..1854c3eb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/FileUtils.java @@ -0,0 +1,68 @@ +package com.xboe.module.scorm.cam.load; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class FileUtils { + + public static List<File> getAllFiles(File srcFile) { + List<File> fileList = new ArrayList<>(); + File[] files = srcFile.listFiles(); + for (File f : Objects.requireNonNull(files)) { + if (f.isFile()) { + fileList.add(f); + } + if (f.isDirectory()) { + if (Objects.requireNonNull(f.listFiles()).length != 0) { + fileList.addAll(getAllFiles(f)); + } else { + fileList.add(f); + } + } + } + return fileList; + } + + public static String getRelativePath(String dirPath, File file) { + File dir = new File(dirPath); + String relativePath = file.getName(); + while (true) { + file = file.getParentFile(); + if (file == null) { + break; + } + if (file.equals(dir)) { + break; + } else { + relativePath = file.getName() + "/" + relativePath; + } + } + return relativePath; + } + + public static File createFile(String dstPath, String fileName) throws IOException { + String[] dirs = fileName.split("/"); + File file = new File(dstPath); + + if (dirs.length > 1) { + for (int i = 0; i < dirs.length - 1; i++) { + file = new File(file, dirs[i]); + } + if (!file.exists()) { + file.mkdirs(); + } + file = new File(file, dirs[dirs.length - 1]); + } else { + if (!file.exists()) { + file.mkdirs(); + } + file = new File(file, dirs[0]); + } + return file; + } + + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ModelUtils.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ModelUtils.java new file mode 100644 index 00000000..db588574 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ModelUtils.java @@ -0,0 +1,183 @@ +package com.xboe.module.scorm.cam.load; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Arrays; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.cardme.engine.VCardEngine; +import net.sourceforge.cardme.vcard.VCardImpl; +import net.sourceforge.cardme.vcard.exceptions.VCardParseException; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; +import com.xboe.module.scorm.cam.model.datatype.Decimal; +import com.xboe.module.scorm.cam.model.datatype.ID; +import com.xboe.module.scorm.cam.model.datatype.IDRef; +import com.xboe.module.scorm.cam.model.datatype.NonNegativeInteger; +import com.xboe.module.scorm.cam.model.datatype.Token; +import com.xboe.module.scorm.cam.model.datatype.VCard; +import com.xboe.module.scorm.common.CommonUtils; + +public class ModelUtils { + + public static boolean isAnyUriEmpty(AnyURI anyURI) { + if (anyURI != null) { + return StringUtils.isBlank(anyURI.getValue()); + } else { + return true; + } + } + + public static boolean isAnyUriFormatCorrect(AnyURI anyURI) { + if (!isAnyUriEmpty(anyURI)) { + return CommonUtils.isLegalURI(anyURI.getValue()); + } else { + return false; + } + } + + public static boolean isDecimalEmpty(Decimal decimal) { + if (decimal != null) { + return StringUtils.isBlank(decimal.getValue()); + } else { + return true; + } + } + + public static boolean isDecimalInRange(Decimal decimal, double minimum, double maximum, int scale) { + if (!isDecimalEmpty(decimal)) { + BigDecimal min = new BigDecimal(minimum).setScale(scale, BigDecimal.ROUND_HALF_UP); + BigDecimal max = new BigDecimal(maximum).setScale(scale, BigDecimal.ROUND_HALF_UP); + BigDecimal num = BigDecimal.valueOf(Double.valueOf(decimal.getValue())) + .setScale(scale, BigDecimal.ROUND_HALF_UP); + return min.compareTo(num) <= 0 && num.compareTo(max) <= 0; + } else { + return false; + } + } + + public static boolean isIDEmpty(ID id) { + if (id != null) { + return StringUtils.isBlank(id.getValue()); + } else { + return true; + } + } + + public static boolean isIDRefEmpty(IDRef idRef) { + if (idRef != null) { + return StringUtils.isBlank(idRef.getValue()); + } else { + return true; + } + } + + public static boolean isNonNegativeIntegerEmpty(NonNegativeInteger nonNegativeInteger) { + if (nonNegativeInteger != null) { + return StringUtils.isBlank(nonNegativeInteger.getValue()); + } else { + return true; + } + } + + public static boolean isNonNegativeIntegerInRange(NonNegativeInteger nonNegativeInteger, int min, int max) { + if (!isNonNegativeIntegerEmpty(nonNegativeInteger)) { + return min <= nonNegativeInteger.getIntValue() && nonNegativeInteger.getIntValue() <= max; + } else { + return false; + } + } + + public static boolean isTokenEmpty(Token token) { + if (token != null) { + return StringUtils.isBlank(token.getValue()); + } else { + return true; + } + } + + public static boolean isLegalToken(Token token, String... vocabularyTable) { + if (!isTokenEmpty(token)) { + return Arrays.asList(vocabularyTable).contains(token.getValue()); + } else { + return false; + } + } + + public static boolean isLegalVocabulary(String data, String... vocabularyTable) { + if (StringUtils.isNotBlank(data)) { + return Arrays.asList(vocabularyTable).contains(data); + } else { + return false; + } + } + + public static boolean isVCardEmpty(VCard vCard) { + if (vCard != null) { + return StringUtils.isBlank(vCard.getValue()); + } else { + return true; + } + } + + public static boolean isLegalVCard(VCard vCard) { + if (!isVCardEmpty(vCard)) { + try { + return ((VCardImpl) new VCardEngine().parse(vCard.getValue())).getErrors().size() == 0; + } catch (IOException | VCardParseException e) { + return false; + } + } else { + return false; + } + } + + public static boolean isDateTimeFormatCorrect(String dateTime) { + if (!StringUtils.isBlank(dateTime)) { + return CommonUtils.isLegalTime(dateTime); + } else { + return false; + } + } + + public static boolean isDurationFormatCorrect(String duration) { + if (!StringUtils.isBlank(duration)) { + return CommonUtils.isLegalDuration(duration); + } else { + return false; + } + } + + public static boolean isParametersFormatCorrect(String parameters) { + String testURI = concatURI("http://www.t.org", parameters); + return isAnyUriFormatCorrect(testURI == null ? null : new AnyURI(testURI)); + } + + public static String concatURI(String uri, String parameters) { + if (StringUtils.isAnyBlank(uri, parameters)) { + return null; + } + while (parameters.charAt(0) == '?' || parameters.charAt(0) == '&') { + parameters = parameters.substring(1); + } + if (parameters.charAt(0) == '#') { + if (!uri.contains("#")) { + uri += parameters; + } + return uri; + } + if (uri.contains("?")) { + uri += "&"; + } else { + uri += "?"; + } + uri += parameters; + return uri; + } + + public static boolean isLegalLanguage(String language) { + return CommonUtils.isLegalLanguage(language); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/SCORMPackageManager.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/SCORMPackageManager.java new file mode 100644 index 00000000..79acb567 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/SCORMPackageManager.java @@ -0,0 +1,151 @@ +package com.xboe.module.scorm.cam.load; + +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.Resource; +import com.xboe.module.scorm.common.LMSPersistDriverManager; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +public class SCORMPackageManager { + + private static SCORMPackageManager instance; + + private Map<String, ContentPackage> contentPackageMap; + + private LMSPersistDriverManager lmsPersistDriverManager; + + private SCORMPackageManager() { + contentPackageMap = new ConcurrentHashMap<>(); + lmsPersistDriverManager = LMSPersistDriverManager.getInstance(); + } + + public static SCORMPackageManager getInstance() { + if (instance == null) { + synchronized (SCORMPackageManager.class) { + if (instance == null) { + instance = new SCORMPackageManager(); + } + } + } + return instance; + } + + public ContentPackage launch(String lmsContentPackageID) { + return launch(lmsContentPackageID, false); + } + + + public ContentPackage launch(String lmsContentPackageID, boolean reloadIfPresent) { + ContentPackage contentPackage = contentPackageMap.get(lmsContentPackageID); + if (contentPackage != null && !reloadIfPresent) { + return contentPackage; + } + if (lmsPersistDriverManager.getDriver() == null) { + log.error("not found lms persist driver"); + return null; + } + String zipFilePath = lmsPersistDriverManager.getDriver().querySCORMPackageZipFilePathBy(lmsContentPackageID); + if (zipFilePath == null) { + log.error("not found scorm package for {}", lmsContentPackageID); + return null; + } + contentPackage = loadSCORMContentPackageFromZipFile(lmsContentPackageID, zipFilePath); + if (contentPackage == null) { + log.error("load scorm content package error"); + return null; + } + return contentPackage; + } + + public void unlaunch(String lmsContentPackageID) { + ContentPackage contentPackage = contentPackageMap.remove(lmsContentPackageID); + if (contentPackage != null) { + try { + FileUtils.deleteDirectory(new File(contentPackage.getBasePath())); + } catch (IOException ignored) { + + } + } + } + + private ContentPackage loadSCORMContentPackageFromZipFile(String lmsContentPackageID, String zipFilePath) { + if (!ZipUtils.isEndWithZip(zipFilePath)) { + return null; + } + + // step 1: uncompress + String saveDir = zipFilePath.substring(0, zipFilePath.length() - 4) + "/"; + if (!ZipUtils.decompressZip(zipFilePath, saveDir)) { + log.error("uncompress error."); + return null; + } + + // step 2: validate all xml file in saveDir + //noinspection ResultOfMethodCallIgnored + if (!XmlSchemaValidator.validateAllXmlFileWithSchemaFileInPath(saveDir)) { + log.error("validate xml schema error."); + return null; + } + + // step 3: 读取imsmanifest.xml,生成ContentPackage + ContentPackage contentPackage = new ContentPackageGenerator().generateContentPackageFromFile(saveDir); + if (contentPackage == null) { + log.error("generate content package error."); + return null; + } + + // step 4: 验证ContentPackage +// ContentPackageValidator validator = new ContentPackageValidator(false); +// boolean validateResult = validator.validate(contentPackage, saveDir); +// if (!validateResult) { +// log.error("validate content package error: " + validator.getErrors()); +// return null; +// } + + contentPackageMap.put(lmsContentPackageID, contentPackage); + + return contentPackage; + } + + public int contentPackageCount() { + return contentPackageMap.size(); + } + + public static void main(String[] args) { +// System.out.println(SCORMPackageManager.getInstance() +// .loadSCORMContentPackageFromZipFile("1", "learningserver-scorm/scorm-test-pkg.zip") +// .getContent()); + System.out.println("test"); + ContentPackage cp=SCORMPackageManager.getInstance().loadSCORMContentPackageFromZipFile("1", "E:/Projects/BOEU/scorm/file/test.zip"); + System.out.println(cp.getManifest().getIdentifier()); + System.out.println(cp.getContent()); + System.out.println("title="+cp.getManifest().getOrganizations().getOrganizationList().get(0).getTitle()); + for(Resource res : cp.getManifest().getResources().getResourceList()) { + System.out.print(res.getHref()); + } + +// System.out.println("ADL_Maritime_Navigation"); +// cp=SCORMPackageManager.getInstance().loadSCORMContentPackageFromZipFile("1", "E:/Projects/BOEU/scorm/file/ADL_Maritime_Navigation.zip"); +// System.out.println(cp.getManifest().getIdentifier()); +// System.out.println(cp.getContent()); +// for(Resource res : cp.getManifest().getResources().getResourceList()) { +// System.out.print(res.getHref()); +// } +// +// System.out.println("7a462dbee222ba62810191d2f512576e"); +// ContentPackage cp3=SCORMPackageManager.getInstance().loadSCORMContentPackageFromZipFile("1", "E:/Projects/BOEU/scorm/file/7a462dbee222ba62810191d2f512576e.zip"); +// System.out.println(cp3.getManifest().getIdentifier()); +// System.out.println(cp3.getContent()); +// for(Resource res : cp3.getManifest().getResources().getResourceList()) { +// System.out.print(res.getHref()); +// } +// + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/XmlSchemaValidator.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/XmlSchemaValidator.java new file mode 100644 index 00000000..816e8ed1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/XmlSchemaValidator.java @@ -0,0 +1,10 @@ +package com.xboe.module.scorm.cam.load; + +public class XmlSchemaValidator { + + public static boolean validateAllXmlFileWithSchemaFileInPath(String path) { + // TODO + return true; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ZipUtils.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ZipUtils.java new file mode 100644 index 00000000..076b3735 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/load/ZipUtils.java @@ -0,0 +1,106 @@ +package com.xboe.module.scorm.cam.load; + + +import java.io.BufferedInputStream; +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.nio.charset.Charset; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class ZipUtils { + + private static final int BUFFER_SIZE = 1024; + + /** + * 未进行测试 + */ + public static boolean compressZip(String srcPath, String zipFilePath) { + if (!isEndWithZip(zipFilePath)) { + zipFilePath += ".zip"; + } + File srcFile = new File(srcPath); + List<File> fileList = FileUtils.getAllFiles(srcFile); + byte[] buffer = new byte[BUFFER_SIZE]; + ZipEntry zipEntry; + int readLen; + + try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFilePath))){ + for (File file : fileList) { + if (file.isFile()) { + zipEntry = new ZipEntry(FileUtils.getRelativePath(srcPath, file)); + zipEntry.setSize(file.length()); + zipEntry.setTime(file.lastModified()); + zipOutputStream.putNextEntry(zipEntry); + + InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + + while ((readLen = inputStream.read(buffer, 0, BUFFER_SIZE)) != -1) { + zipOutputStream.write(buffer, 0, readLen); + } + + inputStream.close(); + } else { + zipEntry = new ZipEntry(FileUtils.getRelativePath(srcPath, file) + "/"); + zipOutputStream.putNextEntry(zipEntry); + } + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + return true; + } + + public static boolean decompressZip(String zipFilePath, String saveFileDir) { + if (!isEndWithZip(zipFilePath)) { + return false; + } + if (!new File(zipFilePath).exists()) { + return false; + } + if (!saveFileDir.endsWith("/")) { + saveFileDir += "/"; + } + try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath),Charset.forName("GBK"))) { + ZipEntry zipEntry; + byte[] buffer = new byte[BUFFER_SIZE]; + int readLen; + + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (zipEntry.isDirectory()) { + File dir = new File(saveFileDir + zipEntry.getName()); + if (!dir.exists()) { + dir.mkdirs(); + } + } else { + File file = FileUtils.createFile(saveFileDir, zipEntry.getName()); + OutputStream outputStream = new FileOutputStream(file); + + while ((readLen = zipInputStream.read(buffer, 0, BUFFER_SIZE)) != -1) { + outputStream.write(buffer, 0, readLen); + } + + outputStream.close(); + } + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public static boolean isEndWithZip(String zipFilePath) { + return zipFilePath != null && !"".equals(zipFilePath.trim()) + && (zipFilePath.endsWith(".ZIP") || zipFilePath.endsWith(".zip")); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqMapInfo.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqMapInfo.java new file mode 100644 index 00000000..d6107946 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqMapInfo.java @@ -0,0 +1,181 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; + +public class AdlseqMapInfo { + + // attributes + private AnyURI targetObjectiveID; // M + private boolean readRawScore; // O true + private boolean readMinScore; // O true + private boolean readMaxScore; // O true + private boolean readCompletionStatus; // O true + private boolean readProgressMeasure; // O true + private boolean writeRawScore; // O false + private boolean writeMinScore; // O false + private boolean writeMaxScore; // O false + private boolean writeCompletionStatus; // O false + private boolean writeProgressMeasure; // O false + + public AdlseqMapInfo() { + readRawScore = true; + readMinScore = true; + readMaxScore = true; + readCompletionStatus = true; + readProgressMeasure = true; + writeRawScore = false; + writeMinScore = false; + writeMaxScore = false; + writeCompletionStatus = false; + writeProgressMeasure = false; + } + + public AnyURI getTargetObjectiveID() { + return targetObjectiveID; + } + + public void setTargetObjectiveID(AnyURI targetObjectiveID) { + this.targetObjectiveID = targetObjectiveID; + } + + public boolean isReadRawScore() { + return readRawScore; + } + + public void setReadRawScore(boolean readRawScore) { + this.readRawScore = readRawScore; + } + + public boolean isReadMinScore() { + return readMinScore; + } + + public void setReadMinScore(boolean readMinScore) { + this.readMinScore = readMinScore; + } + + public boolean isReadMaxScore() { + return readMaxScore; + } + + public void setReadMaxScore(boolean readMaxScore) { + this.readMaxScore = readMaxScore; + } + + public boolean isReadCompletionStatus() { + return readCompletionStatus; + } + + public void setReadCompletionStatus(boolean readCompletionStatus) { + this.readCompletionStatus = readCompletionStatus; + } + + public boolean isReadProgressMeasure() { + return readProgressMeasure; + } + + public void setReadProgressMeasure(boolean readProgressMeasure) { + this.readProgressMeasure = readProgressMeasure; + } + + public boolean isWriteRawScore() { + return writeRawScore; + } + + public void setWriteRawScore(boolean writeRawScore) { + this.writeRawScore = writeRawScore; + } + + public boolean isWriteMinScore() { + return writeMinScore; + } + + public void setWriteMinScore(boolean writeMinScore) { + this.writeMinScore = writeMinScore; + } + + public boolean isWriteMaxScore() { + return writeMaxScore; + } + + public void setWriteMaxScore(boolean writeMaxScore) { + this.writeMaxScore = writeMaxScore; + } + + public boolean isWriteCompletionStatus() { + return writeCompletionStatus; + } + + public void setWriteCompletionStatus(boolean writeCompletionStatus) { + this.writeCompletionStatus = writeCompletionStatus; + } + + public boolean isWriteProgressMeasure() { + return writeProgressMeasure; + } + + public void setWriteProgressMeasure(boolean writeProgressMeasure) { + this.writeProgressMeasure = writeProgressMeasure; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("targetObjectiveID", targetObjectiveID) + .append("readRawScore", readRawScore) + .append("readMinScore", readMinScore) + .append("readMaxScore", readMaxScore) + .append("readCompletionStatus", readCompletionStatus) + .append("readProgressMeasure", readProgressMeasure) + .append("writeRawScore", writeRawScore) + .append("writeMinScore", writeMinScore) + .append("writeMaxScore", writeMaxScore) + .append("writeCompletionStatus", writeCompletionStatus) + .append("writeProgressMeasure", writeProgressMeasure) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + AdlseqMapInfo that = (AdlseqMapInfo) o; + + return new EqualsBuilder() + .append(readRawScore, that.readRawScore) + .append(readMinScore, that.readMinScore) + .append(readMaxScore, that.readMaxScore) + .append(readCompletionStatus, that.readCompletionStatus) + .append(readProgressMeasure, that.readProgressMeasure) + .append(writeRawScore, that.writeRawScore) + .append(writeMinScore, that.writeMinScore) + .append(writeMaxScore, that.writeMaxScore) + .append(writeCompletionStatus, that.writeCompletionStatus) + .append(writeProgressMeasure, that.writeProgressMeasure) + .append(targetObjectiveID, that.targetObjectiveID) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(targetObjectiveID) + .append(readRawScore) + .append(readMinScore) + .append(readMaxScore) + .append(readCompletionStatus) + .append(readProgressMeasure) + .append(writeRawScore) + .append(writeMinScore) + .append(writeMaxScore) + .append(writeCompletionStatus) + .append(writeProgressMeasure) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqObjective.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqObjective.java new file mode 100644 index 00000000..ef07bc2e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqObjective.java @@ -0,0 +1,69 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; + +public class AdlseqObjective { + + // attributes + private AnyURI objectiveID; // M + + // elements + private List<AdlseqMapInfo> mapInfoList; // 1...n + + public AdlseqObjective() { + mapInfoList = new ArrayList<>(); + } + + public AnyURI getObjectiveID() { + return objectiveID; + } + + public void setObjectiveID(AnyURI objectiveID) { + this.objectiveID = objectiveID; + } + + public List<AdlseqMapInfo> getMapInfoList() { + return mapInfoList; + } + + public void setMapInfoList(List<AdlseqMapInfo> mapInfoList) { + this.mapInfoList = mapInfoList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("objectiveID", objectiveID) + .append("mapInfoList", mapInfoList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + AdlseqObjective that = (AdlseqObjective) o; + + return new EqualsBuilder() + .append(objectiveID, that.objectiveID) + .append(mapInfoList, that.mapInfoList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(objectiveID) + .append(mapInfoList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqObjectives.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqObjectives.java new file mode 100644 index 00000000..59f96e13 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AdlseqObjectives.java @@ -0,0 +1,53 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class AdlseqObjectives { + + // elements + private List<AdlseqObjective> objectiveList; // 1...n + + public AdlseqObjectives() { + objectiveList = new ArrayList<>(); + } + + public List<AdlseqObjective> getObjectiveList() { + return objectiveList; + } + + public void setObjectiveList(List<AdlseqObjective> objectiveList) { + this.objectiveList = objectiveList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("objectiveList", objectiveList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + AdlseqObjectives that = (AdlseqObjectives) o; + + return new EqualsBuilder() + .append(objectiveList, that.objectiveList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(objectiveList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Annotation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Annotation.java new file mode 100644 index 00000000..87c3ab78 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Annotation.java @@ -0,0 +1,74 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.VCard; + +public class Annotation { + + private VCard entity; // 0...1 + private DateTime date; // 0...1 + private LanguageStrings description; // 0...1 + + public Annotation() { + } + + public VCard getEntity() { + return entity; + } + + public void setEntity(VCard entity) { + this.entity = entity; + } + + public DateTime getDate() { + return date; + } + + public void setDate(DateTime date) { + this.date = date; + } + + public LanguageStrings getDescription() { + return description; + } + + public void setDescription(LanguageStrings description) { + this.description = description; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("entity", entity) + .append("date", date) + .append("description", description) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Annotation that = (Annotation) o; + + return new EqualsBuilder() + .append(entity, that.entity) + .append(date, that.date) + .append(description, that.description) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(entity) + .append(date) + .append(description) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AuxiliaryResources.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AuxiliaryResources.java new file mode 100644 index 00000000..a4896b3f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/AuxiliaryResources.java @@ -0,0 +1,8 @@ +package com.xboe.module.scorm.cam.model; + +/** + * don't implementation + */ +public class AuxiliaryResources { + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Classification.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Classification.java new file mode 100644 index 00000000..4b8b28b9 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Classification.java @@ -0,0 +1,89 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Classification { + + private Vocabulary purpose; // 0...1 + private List<TaxonPath> taxonPathList; // 0...n + private LanguageStrings description; // 0...1 + private List<LanguageStrings> keywordList; // 0...n + + public Classification() { + taxonPathList = new ArrayList<>(); + keywordList = new ArrayList<>(); + } + + public Vocabulary getPurpose() { + return purpose; + } + + public void setPurpose(Vocabulary purpose) { + this.purpose = purpose; + } + + public List<TaxonPath> getTaxonPathList() { + return taxonPathList; + } + + public void setTaxonPathList(List<TaxonPath> taxonPathList) { + this.taxonPathList = taxonPathList; + } + + public LanguageStrings getDescription() { + return description; + } + + public void setDescription(LanguageStrings description) { + this.description = description; + } + + public List<LanguageStrings> getKeywordList() { + return keywordList; + } + + public void setKeywordList(List<LanguageStrings> keywordList) { + this.keywordList = keywordList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("purpose", purpose) + .append("taxonPathList", taxonPathList) + .append("description", description) + .append("keywordList", keywordList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Classification that = (Classification) o; + + return new EqualsBuilder() + .append(purpose, that.purpose) + .append(taxonPathList, that.taxonPathList) + .append(description, that.description) + .append(keywordList, that.keywordList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(purpose) + .append(taxonPathList) + .append(description) + .append(keywordList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/CompletionThreshold.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/CompletionThreshold.java new file mode 100644 index 00000000..a28730e4 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/CompletionThreshold.java @@ -0,0 +1,78 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Decimal; + +public class CompletionThreshold { + + // attributes + private boolean completedByMeasure; // O false + private Decimal minProgressMeasure; // O 1.0 [0.0000,1.0000] + private Decimal progressWeight; // O 1.0 [0.0000,1.0000] + + public CompletionThreshold() { + completedByMeasure = false; + minProgressMeasure = new Decimal("1.0", 4); + progressWeight = new Decimal("1.0", 4); + } + + public boolean isCompletedByMeasure() { + return completedByMeasure; + } + + public void setCompletedByMeasure(boolean completedByMeasure) { + this.completedByMeasure = completedByMeasure; + } + + public Decimal getMinProgressMeasure() { + return minProgressMeasure; + } + + public void setMinProgressMeasure(Decimal minProgressMeasure) { + this.minProgressMeasure = minProgressMeasure; + } + + public Decimal getProgressWeight() { + return progressWeight; + } + + public void setProgressWeight(Decimal progressWeight) { + this.progressWeight = progressWeight; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("completedByMeasure", completedByMeasure) + .append("minProgressMeasure", minProgressMeasure) + .append("progressWeight", progressWeight) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + CompletionThreshold that = (CompletionThreshold) o; + + return new EqualsBuilder() + .append(completedByMeasure, that.completedByMeasure) + .append(minProgressMeasure, that.minProgressMeasure) + .append(progressWeight, that.progressWeight) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(completedByMeasure) + .append(minProgressMeasure) + .append(progressWeight) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ConditionRule.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ConditionRule.java new file mode 100644 index 00000000..dd674149 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ConditionRule.java @@ -0,0 +1,60 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class ConditionRule { + + private RuleConditions ruleConditions; // 1...1 + private RuleAction ruleAction; // 1...1 + + public ConditionRule() { + } + + public RuleConditions getRuleConditions() { + return ruleConditions; + } + + public void setRuleConditions(RuleConditions ruleConditions) { + this.ruleConditions = ruleConditions; + } + + public RuleAction getRuleAction() { + return ruleAction; + } + + public void setRuleAction(RuleAction ruleAction) { + this.ruleAction = ruleAction; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("ruleConditions", ruleConditions) + .append("ruleAction", ruleAction) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ConditionRule that = (ConditionRule) o; + + return new EqualsBuilder() + .append(ruleConditions, that.ruleConditions) + .append(ruleAction, that.ruleAction) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(ruleConditions) + .append(ruleAction) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ConstrainedChoiceConsiderations.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ConstrainedChoiceConsiderations.java new file mode 100644 index 00000000..1bd5dadb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ConstrainedChoiceConsiderations.java @@ -0,0 +1,63 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class ConstrainedChoiceConsiderations { + + // attributes + private boolean preventActivation; // O false + private boolean constrainChoice; // O false + + public ConstrainedChoiceConsiderations() { + preventActivation = false; + constrainChoice = false; + } + + public boolean isPreventActivation() { + return preventActivation; + } + + public void setPreventActivation(boolean preventActivation) { + this.preventActivation = preventActivation; + } + + public boolean isConstrainChoice() { + return constrainChoice; + } + + public void setConstrainChoice(boolean constrainChoice) { + this.constrainChoice = constrainChoice; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("preventActivation", preventActivation) + .append("constrainChoice", constrainChoice) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ConstrainedChoiceConsiderations that = (ConstrainedChoiceConsiderations) o; + + return new EqualsBuilder() + .append(preventActivation, that.preventActivation) + .append(constrainChoice, that.constrainChoice) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(preventActivation) + .append(constrainChoice) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Content.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Content.java new file mode 100644 index 00000000..940a27fa --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Content.java @@ -0,0 +1,52 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Content { + + private List<String> physicalFilePathList; + + public Content() { + physicalFilePathList = new ArrayList<>(); + } + + public List<String> getPhysicalFilePathList() { + return physicalFilePathList; + } + + public void setPhysicalFilePathList(List<String> physicalFilePathList) { + this.physicalFilePathList = physicalFilePathList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("physicalFilePathList", physicalFilePathList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Content content = (Content) o; + + return new EqualsBuilder() + .append(physicalFilePathList, content.physicalFilePathList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(physicalFilePathList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ContentPackage.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ContentPackage.java new file mode 100644 index 00000000..39f5aabc --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ContentPackage.java @@ -0,0 +1,146 @@ +package com.xboe.module.scorm.cam.model; + +import com.xboe.module.scorm.cam.load.ModelUtils; +import com.xboe.module.scorm.cam.model.util.CPUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * A package represents a unit of learning. + */ +public class ContentPackage { + + private final String basePath; + + private Manifest manifest; // 1...1 + + public ContentPackage(String basePath) { + this.basePath = basePath; + } + + public Manifest getManifest() { + return manifest; + } + + public void setManifest(Manifest manifest) { + this.manifest = manifest; + } + + public Content getContent() { + // 根据manifest自动解析 + if (manifest == null || manifest.getResources() == null) { + return null; + } + final Content content = new Content(); + final String manifestXmlBase = !ModelUtils.isAnyUriEmpty(manifest.getXmlBase()) ? manifest.getXmlBase().getValue() : ""; + final String resourcesXmlBase = !ModelUtils.isAnyUriEmpty(manifest.getResources().getXmlBase()) ? + manifest.getResources().getXmlBase().getValue() : ""; + manifest.getResources().getResourceList().forEach(resource -> { + final String resourceXmlBase = !ModelUtils.isAnyUriEmpty(resource.getXmlBase()) ? resource.getXmlBase().getValue() : ""; + resource.getFileList().forEach(file -> { + if (StringUtils.isNotBlank(file.getHref())) { + content.getPhysicalFilePathList().add(manifestXmlBase + resourcesXmlBase + resourceXmlBase + file.getHref()); + } + }); + }); + return content; + } + + public DeliveryContent getDeliveryContent(String itemID) { + Item item = CPUtils.findItemByIdentifier(this, itemID); + if (item == null || !item.getItemList().isEmpty() || StringUtils.isBlank(item.getIdentifierref())) { + return null; + } + Resource resourceRefByItem = CPUtils.findResource(this, item.getIdentifierref()); + if (resourceRefByItem == null) { + return null; + } + List<Resource> resources = new LinkedList<>(); + resources.add(resourceRefByItem); + CPUtils.findResource(this, resourceRefByItem, resources); + Set<String> resourceIDSet = new HashSet<>(); + List<Resource> resourceList = new LinkedList<>(); + for (Resource res : resources) { + if (!resourceIDSet.contains(res.getIdentifier().getValue())) { + resourceIDSet.add(res.getIdentifier().getValue()); + resourceList.add(res); + } + } + final Content content = new Content(); + final String manifestXmlBase = !ModelUtils.isAnyUriEmpty(manifest.getXmlBase()) ? manifest.getXmlBase().getValue() : ""; + final String resourcesXmlBase = !ModelUtils.isAnyUriEmpty(manifest.getResources().getXmlBase()) ? + manifest.getResources().getXmlBase().getValue() : ""; + resourceList.forEach(resource -> { + final String resourceXmlBase = !ModelUtils.isAnyUriEmpty(resource.getXmlBase()) ? resource.getXmlBase().getValue() : ""; + resource.getFileList().forEach(file -> { + if (StringUtils.isNotBlank(file.getHref())) { + content.getPhysicalFilePathList().add(manifestXmlBase + resourcesXmlBase + resourceXmlBase + file.getHref()); + } + }); + }); + String entry = manifestXmlBase + resourcesXmlBase + + (!ModelUtils.isAnyUriEmpty(resourceRefByItem.getXmlBase()) ? resourceRefByItem.getXmlBase().getValue() : "") + + resourceRefByItem.getHref(); + return new DeliveryContent(itemID, basePath, entry, item.getParameters(), content); + } + + public ContentPackageType getContentPackageType() { + if (manifest == null || manifest.getOrganizations() == null) { + return null; + } + ContentPackageType contentPackageType; + // 根据manifest.organizations是否为空,设置type + if (manifest.getOrganizations().getOrganizationList().isEmpty()) { + contentPackageType = ContentPackageType.RESOURCE_CONTENT_PACKAGE; + } else { + contentPackageType = ContentPackageType.CONTENT_AGGREGATION_CONTENT_PACKAGE; + } + return contentPackageType; + } + + public String getBasePath() { + return basePath; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("basePath", basePath) + .append("manifest", manifest) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ContentPackage that = (ContentPackage) o; + + return new EqualsBuilder() + .append(basePath, that.basePath) + .append(manifest, that.manifest) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(basePath) + .append(manifest) + .toHashCode(); + } + + public enum ContentPackageType { + CONTENT_AGGREGATION_CONTENT_PACKAGE, + RESOURCE_CONTENT_PACKAGE; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Contribute.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Contribute.java new file mode 100644 index 00000000..66adad19 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Contribute.java @@ -0,0 +1,78 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.VCard; + +public class Contribute { + + private Vocabulary role; // 0...1 + private List<VCard> entityList; // 0...n + private DateTime date; // 0...1 + + public Contribute() { + entityList = new ArrayList<>(); + } + + public Vocabulary getRole() { + return role; + } + + public void setRole(Vocabulary role) { + this.role = role; + } + + public List<VCard> getEntityList() { + return entityList; + } + + public void setEntityList(List<VCard> entityList) { + this.entityList = entityList; + } + + public DateTime getDate() { + return date; + } + + public void setDate(DateTime date) { + this.date = date; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("role", role) + .append("entityList", entityList) + .append("date", date) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Contribute that = (Contribute) o; + + return new EqualsBuilder() + .append(role, that.role) + .append(entityList, that.entityList) + .append(date, that.date) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(role) + .append(entityList) + .append(date) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ControlMode.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ControlMode.java new file mode 100644 index 00000000..dd93d381 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ControlMode.java @@ -0,0 +1,115 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class ControlMode { + + // attributes + private boolean choice; // O true + private boolean choiceExit; // O true + private boolean flow; // O false + private boolean forwardOnly; // O false + private boolean useCurrentAttemptObjectiveInfo; // O true + private boolean useCurrentAttemptProgressInfo; // O true + + public ControlMode() { + choice = true; + choiceExit = true; + flow = false; + forwardOnly = false; + useCurrentAttemptObjectiveInfo = true; + useCurrentAttemptProgressInfo = true; + } + + public boolean isChoice() { + return choice; + } + + public void setChoice(boolean choice) { + this.choice = choice; + } + + public boolean isChoiceExit() { + return choiceExit; + } + + public void setChoiceExit(boolean choiceExit) { + this.choiceExit = choiceExit; + } + + public boolean isFlow() { + return flow; + } + + public void setFlow(boolean flow) { + this.flow = flow; + } + + public boolean isForwardOnly() { + return forwardOnly; + } + + public void setForwardOnly(boolean forwardOnly) { + this.forwardOnly = forwardOnly; + } + + public boolean isUseCurrentAttemptObjectiveInfo() { + return useCurrentAttemptObjectiveInfo; + } + + public void setUseCurrentAttemptObjectiveInfo(boolean useCurrentAttemptObjectiveInfo) { + this.useCurrentAttemptObjectiveInfo = useCurrentAttemptObjectiveInfo; + } + + public boolean isUseCurrentAttemptProgressInfo() { + return useCurrentAttemptProgressInfo; + } + + public void setUseCurrentAttemptProgressInfo(boolean useCurrentAttemptProgressInfo) { + this.useCurrentAttemptProgressInfo = useCurrentAttemptProgressInfo; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("choice", choice) + .append("choiceExit", choiceExit) + .append("flow", flow) + .append("forwardOnly", forwardOnly) + .append("useCurrentAttemptObjectiveInfo", useCurrentAttemptObjectiveInfo) + .append("useCurrentAttemptProgressInfo", useCurrentAttemptProgressInfo) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ControlMode that = (ControlMode) o; + + return new EqualsBuilder() + .append(choice, that.choice) + .append(choiceExit, that.choiceExit) + .append(flow, that.flow) + .append(forwardOnly, that.forwardOnly) + .append(useCurrentAttemptObjectiveInfo, that.useCurrentAttemptObjectiveInfo) + .append(useCurrentAttemptProgressInfo, that.useCurrentAttemptProgressInfo) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(choice) + .append(choiceExit) + .append(flow) + .append(forwardOnly) + .append(useCurrentAttemptObjectiveInfo) + .append(useCurrentAttemptProgressInfo) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Data.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Data.java new file mode 100644 index 00000000..837bad65 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Data.java @@ -0,0 +1,53 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Data { + + // elements + private List<Map> mapList; // 1...n + + public Data() { + mapList = new ArrayList<>(); + } + + public List<Map> getMapList() { + return mapList; + } + + public void setMapList(List<Map> mapList) { + this.mapList = mapList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("mapList", mapList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Data data = (Data) o; + + return new EqualsBuilder() + .append(mapList, data.mapList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(mapList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DateTime.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DateTime.java new file mode 100644 index 00000000..7792711b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DateTime.java @@ -0,0 +1,73 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class DateTime { + + private String dateTime; // 0...1 + private LanguageStrings description; // 0...1 + + public DateTime() { + } + + public DateTime(String dateTime, LanguageStrings description) { + this.dateTime = dateTime; + this.description = description; + } + + public DateTime(String dateTime) { + this.dateTime = dateTime; + } + + public DateTime(LanguageStrings description) { + this.description = description; + } + + public String getDateTime() { + return dateTime; + } + + public void setDateTime(String dateTime) { + this.dateTime = dateTime; + } + + public LanguageStrings getDescription() { + return description; + } + + public void setDescription(LanguageStrings description) { + this.description = description; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("dateTime", dateTime) + .append("description", description) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + DateTime dateTime1 = (DateTime) o; + + return new EqualsBuilder() + .append(dateTime, dateTime1.dateTime) + .append(description, dateTime1.description) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(dateTime) + .append(description) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DeliveryContent.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DeliveryContent.java new file mode 100644 index 00000000..b1db151f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DeliveryContent.java @@ -0,0 +1,42 @@ +package com.xboe.module.scorm.cam.model; + +public class DeliveryContent { + + private final String itemId; + + private final String basePath; + + private final String entry; + + private final String parameters; + + private final Content content; + + public DeliveryContent(String itemId, String basePath, String entry, String parameters, Content content) { + this.itemId = itemId; + this.basePath = basePath; + this.entry = entry; + this.parameters = parameters; + this.content = content; + } + + public String getItemId() { + return itemId; + } + + public String getBasePath() { + return basePath; + } + + public String getEntry() { + return entry; + } + + public String getParameters() { + return parameters; + } + + public Content getContent() { + return content; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DeliveryControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DeliveryControls.java new file mode 100644 index 00000000..12a7c30c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/DeliveryControls.java @@ -0,0 +1,76 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class DeliveryControls { + + // attributes + private boolean tracked; // O true + private boolean completionSetByContent; // O false + private boolean objectiveSetByContent; // O false + + public DeliveryControls() { + tracked = true; + completionSetByContent = false; + objectiveSetByContent = false; + } + + public boolean isTracked() { + return tracked; + } + + public void setTracked(boolean tracked) { + this.tracked = tracked; + } + + public boolean isCompletionSetByContent() { + return completionSetByContent; + } + + public void setCompletionSetByContent(boolean completionSetByContent) { + this.completionSetByContent = completionSetByContent; + } + + public boolean isObjectiveSetByContent() { + return objectiveSetByContent; + } + + public void setObjectiveSetByContent(boolean objectiveSetByContent) { + this.objectiveSetByContent = objectiveSetByContent; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("tracked", tracked) + .append("completionSetByContent", completionSetByContent) + .append("objectiveSetByContent", objectiveSetByContent) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + DeliveryControls that = (DeliveryControls) o; + + return new EqualsBuilder() + .append(tracked, that.tracked) + .append(completionSetByContent, that.completionSetByContent) + .append(objectiveSetByContent, that.objectiveSetByContent) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(tracked) + .append(completionSetByContent) + .append(objectiveSetByContent) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Dependency.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Dependency.java new file mode 100644 index 00000000..becd318c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Dependency.java @@ -0,0 +1,49 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Dependency { + + // attributes + private String identifierref; // M + + public Dependency() { + } + + public String getIdentifierref() { + return identifierref; + } + + public void setIdentifierref(String identifierref) { + this.identifierref = identifierref; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifierref", identifierref) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Dependency that = (Dependency) o; + + return new EqualsBuilder() + .append(identifierref, that.identifierref) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifierref) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Duration.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Duration.java new file mode 100644 index 00000000..d64d53b4 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Duration.java @@ -0,0 +1,73 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Duration { + + private String duration; // 0...1 + private LanguageStrings description; // 0...1 + + public Duration() { + } + + public Duration(String duration) { + this.duration = duration; + } + + public Duration(LanguageStrings description) { + this.description = description; + } + + public Duration(String duration, LanguageStrings description) { + this.duration = duration; + this.description = description; + } + + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } + + public LanguageStrings getDescription() { + return description; + } + + public void setDescription(LanguageStrings description) { + this.description = description; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("duration", duration) + .append("description", description) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Duration duration1 = (Duration) o; + + return new EqualsBuilder() + .append(duration, duration1.duration) + .append(description, duration1.description) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(duration) + .append(description) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Educational.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Educational.java new file mode 100644 index 00000000..679c331e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Educational.java @@ -0,0 +1,170 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Educational { + + private Vocabulary interactivityType; // 0...1 + private List<Vocabulary> learningResourceTypeList; // 0...n + private Vocabulary interactivityLevel; // 0...1 + private Vocabulary semanticDensity; // 0...1 + private List<Vocabulary> intendedEndUserRoleList; // 0...n + private List<Vocabulary> contextList; // 0...n + private List<LanguageStrings> typicalAgeRangeList; // 0...n + private Vocabulary difficulty; // 0...1 + private Duration typicalLearningTime; // 0...1 + private List<LanguageStrings> descriptionList; // 0...n + private List<String> languageList; // 0...n + + public Educational() { + } + + public Vocabulary getInteractivityType() { + return interactivityType; + } + + public void setInteractivityType(Vocabulary interactivityType) { + this.interactivityType = interactivityType; + } + + public List<Vocabulary> getLearningResourceTypeList() { + return learningResourceTypeList; + } + + public void setLearningResourceTypeList(List<Vocabulary> learningResourceTypeList) { + this.learningResourceTypeList = learningResourceTypeList; + } + + public Vocabulary getInteractivityLevel() { + return interactivityLevel; + } + + public void setInteractivityLevel(Vocabulary interactivityLevel) { + this.interactivityLevel = interactivityLevel; + } + + public Vocabulary getSemanticDensity() { + return semanticDensity; + } + + public void setSemanticDensity(Vocabulary semanticDensity) { + this.semanticDensity = semanticDensity; + } + + public List<Vocabulary> getIntendedEndUserRoleList() { + return intendedEndUserRoleList; + } + + public void setIntendedEndUserRoleList(List<Vocabulary> intendedEndUserRoleList) { + this.intendedEndUserRoleList = intendedEndUserRoleList; + } + + public List<Vocabulary> getContextList() { + return contextList; + } + + public void setContextList(List<Vocabulary> contextList) { + this.contextList = contextList; + } + + public List<LanguageStrings> getTypicalAgeRangeList() { + return typicalAgeRangeList; + } + + public void setTypicalAgeRangeList(List<LanguageStrings> typicalAgeRangeList) { + this.typicalAgeRangeList = typicalAgeRangeList; + } + + public Vocabulary getDifficulty() { + return difficulty; + } + + public void setDifficulty(Vocabulary difficulty) { + this.difficulty = difficulty; + } + + public Duration getTypicalLearningTime() { + return typicalLearningTime; + } + + public void setTypicalLearningTime(Duration typicalLearningTime) { + this.typicalLearningTime = typicalLearningTime; + } + + public List<LanguageStrings> getDescriptionList() { + return descriptionList; + } + + public void setDescriptionList(List<LanguageStrings> descriptionList) { + this.descriptionList = descriptionList; + } + + public List<String> getLanguageList() { + return languageList; + } + + public void setLanguageList(List<String> languageList) { + this.languageList = languageList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("interactivityType", interactivityType) + .append("learningResourceTypeList", learningResourceTypeList) + .append("interactivityLevel", interactivityLevel) + .append("semanticDensity", semanticDensity) + .append("intendedEndUserRoleList", intendedEndUserRoleList) + .append("contextList", contextList) + .append("typicalAgeRangeList", typicalAgeRangeList) + .append("difficulty", difficulty) + .append("typicalLearningTime", typicalLearningTime) + .append("descriptionList", descriptionList) + .append("languageList", languageList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Educational that = (Educational) o; + + return new EqualsBuilder() + .append(interactivityType, that.interactivityType) + .append(learningResourceTypeList, that.learningResourceTypeList) + .append(interactivityLevel, that.interactivityLevel) + .append(semanticDensity, that.semanticDensity) + .append(intendedEndUserRoleList, that.intendedEndUserRoleList) + .append(contextList, that.contextList) + .append(typicalAgeRangeList, that.typicalAgeRangeList) + .append(difficulty, that.difficulty) + .append(typicalLearningTime, that.typicalLearningTime) + .append(descriptionList, that.descriptionList) + .append(languageList, that.languageList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(interactivityType) + .append(learningResourceTypeList) + .append(interactivityLevel) + .append(semanticDensity) + .append(intendedEndUserRoleList) + .append(contextList) + .append(typicalAgeRangeList) + .append(difficulty) + .append(typicalLearningTime) + .append(descriptionList) + .append(languageList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/File.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/File.java new file mode 100644 index 00000000..35df56a5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/File.java @@ -0,0 +1,63 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class File { + + // attributes + private String href; // M + + // elements + private Metadata metadata; // 0...1 + + public File() { + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("href", href) + .append("metadata", metadata) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + File file = (File) o; + + return new EqualsBuilder() + .append(href, file.href) + .append(metadata, file.metadata) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(href) + .append(metadata) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/General.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/General.java new file mode 100644 index 00000000..8634bcfb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/General.java @@ -0,0 +1,140 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class General { + + private List<Identifier> identifierList; // 0...n + private LanguageStrings title; // 0...1 + private List<String> languageList; //0...n + private List<LanguageStrings> descriptionList; // 0...n + private List<LanguageStrings> keywordList; // 0...n + private List<LanguageStrings> coverageList; // 0...n + private Vocabulary structure; // 0...1 + private Vocabulary aggregationLevel; // 0...1 + + public General() { + identifierList = new ArrayList<>(); + languageList = new ArrayList<>(); + descriptionList = new ArrayList<>(); + keywordList = new ArrayList<>(); + coverageList = new ArrayList<>(); + } + + public List<Identifier> getIdentifierList() { + return identifierList; + } + + public void setIdentifierList(List<Identifier> identifierList) { + this.identifierList = identifierList; + } + + public LanguageStrings getTitle() { + return title; + } + + public void setTitle(LanguageStrings title) { + this.title = title; + } + + public List<String> getLanguageList() { + return languageList; + } + + public void setLanguageList(List<String> languageList) { + this.languageList = languageList; + } + + public List<LanguageStrings> getDescriptionList() { + return descriptionList; + } + + public void setDescriptionList(List<LanguageStrings> descriptionList) { + this.descriptionList = descriptionList; + } + + public List<LanguageStrings> getKeywordList() { + return keywordList; + } + + public void setKeywordList(List<LanguageStrings> keywordList) { + this.keywordList = keywordList; + } + + public List<LanguageStrings> getCoverageList() { + return coverageList; + } + + public void setCoverageList(List<LanguageStrings> coverageList) { + this.coverageList = coverageList; + } + + public Vocabulary getStructure() { + return structure; + } + + public void setStructure(Vocabulary structure) { + this.structure = structure; + } + + public Vocabulary getAggregationLevel() { + return aggregationLevel; + } + + public void setAggregationLevel(Vocabulary aggregationLevel) { + this.aggregationLevel = aggregationLevel; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifierList", identifierList) + .append("title", title) + .append("languageList", languageList) + .append("descriptionList", descriptionList) + .append("keywordList", keywordList) + .append("coverageList", coverageList) + .append("structure", structure) + .append("aggregationLevel", aggregationLevel) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + General general = (General) o; + + return new EqualsBuilder() + .append(identifierList, general.identifierList) + .append(title, general.title) + .append(languageList, general.languageList) + .append(descriptionList, general.descriptionList) + .append(keywordList, general.keywordList) + .append(coverageList, general.coverageList) + .append(structure, general.structure) + .append(aggregationLevel, general.aggregationLevel) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifierList) + .append(title) + .append(languageList) + .append(descriptionList) + .append(keywordList) + .append(coverageList) + .append(structure) + .append(aggregationLevel) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/HideLMSUI.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/HideLMSUI.java new file mode 100644 index 00000000..4c0fce81 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/HideLMSUI.java @@ -0,0 +1,48 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class HideLMSUI { + + private String value; // M (is a Token) + + public HideLMSUI() { + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("value", value) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + HideLMSUI hideLMSUI = (HideLMSUI) o; + + return new EqualsBuilder() + .append(value, hideLMSUI.value) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(value) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Identifier.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Identifier.java new file mode 100644 index 00000000..08284b7f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Identifier.java @@ -0,0 +1,60 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Identifier { + + private String catalog; // 0...1 + private String entry; // 0...1 + + public Identifier() { + } + + public String getCatalog() { + return catalog; + } + + public void setCatalog(String catalog) { + this.catalog = catalog; + } + + public String getEntry() { + return entry; + } + + public void setEntry(String entry) { + this.entry = entry; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("catalog", catalog) + .append("entry", entry) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Identifier that = (Identifier) o; + + return new EqualsBuilder() + .append(catalog, that.catalog) + .append(entry, that.entry) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(catalog) + .append(entry) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Item.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Item.java new file mode 100644 index 00000000..c3570258 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Item.java @@ -0,0 +1,230 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.ID; + +public class Item { + + // attributes + private ID identifier; // M + private String identifierref; // O + private boolean isvisible; // O true + private String parameters; // O 格式限制 + + // elements + private String title; // 1...1 + private List<Item> itemList; // 0...n + private Metadata metadata; // 0...1 + private String timeLimitAction; // 0...1 {exit,message}{exit,no message}{continue,message}{continue,no message} + private String dataFromLMS; // 0...1 + private CompletionThreshold completionThreshold; // 0...1 + private Data data; // 0...1 + private Sequencing sequencing; // 0...1 + private Presentation presentation; // 0...1 + + // other + private Organization parentOrganization; + private Item parentItem; + + public Item() { + isvisible = true; + itemList = new ArrayList<>(); + } + + public ID getIdentifier() { + return identifier; + } + + public void setIdentifier(ID identifier) { + this.identifier = identifier; + } + + public String getIdentifierref() { + return identifierref; + } + + public void setIdentifierref(String identifierref) { + this.identifierref = identifierref; + } + + public boolean isIsvisible() { + return isvisible; + } + + public void setIsvisible(boolean isvisible) { + this.isvisible = isvisible; + } + + public String getParameters() { + return parameters; + } + + public void setParameters(String parameters) { + this.parameters = parameters; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List<Item> getItemList() { + return itemList; + } + + public void setItemList(List<Item> itemList) { + this.itemList = itemList; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + public String getTimeLimitAction() { + return timeLimitAction; + } + + public void setTimeLimitAction(String timeLimitAction) { + this.timeLimitAction = timeLimitAction; + } + + public String getDataFromLMS() { + return dataFromLMS; + } + + public void setDataFromLMS(String dataFromLMS) { + this.dataFromLMS = dataFromLMS; + } + + public CompletionThreshold getCompletionThreshold() { + return completionThreshold; + } + + public void setCompletionThreshold(CompletionThreshold completionThreshold) { + this.completionThreshold = completionThreshold; + } + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public Sequencing getSequencing() { + return sequencing; + } + + public void setSequencing(Sequencing sequencing) { + this.sequencing = sequencing; + } + + public Presentation getPresentation() { + return presentation; + } + + public void setPresentation(Presentation presentation) { + this.presentation = presentation; + } + + public Organization getParentOrganization() { + return parentOrganization; + } + + public void setParentOrganization(Organization parentOrganization) { + this.parentOrganization = parentOrganization; + } + + public Item getParentItem() { + return parentItem; + } + + public void setParentItem(Item parentItem) { + this.parentItem = parentItem; + } + + public boolean parentIsItem() { + return parentItem != null; + } + + public boolean parentIsOrganization() { + return parentOrganization != null; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifier", identifier) + .append("identifierref", identifierref) + .append("isvisible", isvisible) + .append("parameters", parameters) + .append("title", title) + .append("itemList", itemList) + .append("metadata", metadata) + .append("timeLimitAction", timeLimitAction) + .append("dataFromLMS", dataFromLMS) + .append("completionThreshold", completionThreshold) + .append("data", data) + .append("sequencing", sequencing) + .append("presentation", presentation) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Item item = (Item) o; + + return new EqualsBuilder() + .append(isvisible, item.isvisible) + .append(identifier, item.identifier) + .append(identifierref, item.identifierref) + .append(parameters, item.parameters) + .append(title, item.title) + .append(itemList, item.itemList) + .append(metadata, item.metadata) + .append(timeLimitAction, item.timeLimitAction) + .append(dataFromLMS, item.dataFromLMS) + .append(completionThreshold, item.completionThreshold) + .append(data, item.data) + .append(sequencing, item.sequencing) + .append(presentation, item.presentation) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifier) + .append(identifierref) + .append(isvisible) + .append(parameters) + .append(title) + .append(itemList) + .append(metadata) + .append(timeLimitAction) + .append(dataFromLMS) + .append(completionThreshold) + .append(data) + .append(sequencing) + .append(presentation) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LOM.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LOM.java new file mode 100644 index 00000000..b8f99c1a --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LOM.java @@ -0,0 +1,152 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class LOM { + + // elements + private General general; // 0...1 + private LifeCycle lifeCycle; // 0...1 + private MetaMetadata metaMetadata; // 0...1 + private Technical technical; // 0...1 + private List<Educational> educationalList; // 0...n + private Rights rights; // 0...1 + private List<Relation> relationList; // 0...n + private List<Annotation> annotationList; // 0...n + private List<Classification> classificationList; // 0...n + + public LOM() { + educationalList = new ArrayList<>(); + relationList = new ArrayList<>(); + annotationList = new ArrayList<>(); + classificationList = new ArrayList<>(); + } + + public General getGeneral() { + return general; + } + + public void setGeneral(General general) { + this.general = general; + } + + public LifeCycle getLifeCycle() { + return lifeCycle; + } + + public void setLifeCycle(LifeCycle lifeCycle) { + this.lifeCycle = lifeCycle; + } + + public MetaMetadata getMetaMetadata() { + return metaMetadata; + } + + public void setMetaMetadata(MetaMetadata metaMetadata) { + this.metaMetadata = metaMetadata; + } + + public Technical getTechnical() { + return technical; + } + + public void setTechnical(Technical technical) { + this.technical = technical; + } + + public List<Educational> getEducationalList() { + return educationalList; + } + + public void setEducationalList(List<Educational> educationalList) { + this.educationalList = educationalList; + } + + public Rights getRights() { + return rights; + } + + public void setRights(Rights rights) { + this.rights = rights; + } + + public List<Relation> getRelationList() { + return relationList; + } + + public void setRelationList(List<Relation> relationList) { + this.relationList = relationList; + } + + public List<Annotation> getAnnotationList() { + return annotationList; + } + + public void setAnnotationList(List<Annotation> annotationList) { + this.annotationList = annotationList; + } + + public List<Classification> getClassificationList() { + return classificationList; + } + + public void setClassificationList(List<Classification> classificationList) { + this.classificationList = classificationList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("general", general) + .append("lifeCycle", lifeCycle) + .append("metaMetadata", metaMetadata) + .append("technical", technical) + .append("educationalList", educationalList) + .append("rights", rights) + .append("relationList", relationList) + .append("annotationList", annotationList) + .append("classificationList", classificationList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + LOM lom = (LOM) o; + + return new EqualsBuilder() + .append(general, lom.general) + .append(lifeCycle, lom.lifeCycle) + .append(metaMetadata, lom.metaMetadata) + .append(technical, lom.technical) + .append(educationalList, lom.educationalList) + .append(rights, lom.rights) + .append(relationList, lom.relationList) + .append(annotationList, lom.annotationList) + .append(classificationList, lom.classificationList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(general) + .append(lifeCycle) + .append(metaMetadata) + .append(technical) + .append(educationalList) + .append(rights) + .append(relationList) + .append(annotationList) + .append(classificationList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LanguageString.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LanguageString.java new file mode 100644 index 00000000..781ead36 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LanguageString.java @@ -0,0 +1,62 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class LanguageString { + + private String language; // O + private String value; + + public LanguageString(String language, String value) { + this.language = language; + this.value = value; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("language", language) + .append("value", value) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + LanguageString that = (LanguageString) o; + + return new EqualsBuilder() + .append(language, that.language) + .append(value, that.value) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(language) + .append(value) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LanguageStrings.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LanguageStrings.java new file mode 100644 index 00000000..36c5720d --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LanguageStrings.java @@ -0,0 +1,52 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class LanguageStrings { + + List<LanguageString> languageStringList; + + public LanguageStrings() { + this.languageStringList = new ArrayList<>(); + } + + public List<LanguageString> getLanguageStringList() { + return languageStringList; + } + + public void setLanguageStringList(List<LanguageString> languageStringList) { + this.languageStringList = languageStringList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("languageStringList", languageStringList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + LanguageStrings that = (LanguageStrings) o; + + return new EqualsBuilder() + .append(languageStringList, that.languageStringList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(languageStringList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LifeCycle.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LifeCycle.java new file mode 100644 index 00000000..90c6c62d --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LifeCycle.java @@ -0,0 +1,76 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class LifeCycle { + + private LanguageStrings version; // 0...1 + private Vocabulary status; // 0...1 + private List<Contribute> contributeList; // 0...n + + public LifeCycle() { + contributeList = new ArrayList<>(); + } + + public LanguageStrings getVersion() { + return version; + } + + public void setVersion(LanguageStrings version) { + this.version = version; + } + + public Vocabulary getStatus() { + return status; + } + + public void setStatus(Vocabulary status) { + this.status = status; + } + + public List<Contribute> getContributeList() { + return contributeList; + } + + public void setContributeList(List<Contribute> contributeList) { + this.contributeList = contributeList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("version", version) + .append("status", status) + .append("contributeList", contributeList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + LifeCycle lifeCycle = (LifeCycle) o; + + return new EqualsBuilder() + .append(version, lifeCycle.version) + .append(status, lifeCycle.status) + .append(contributeList, lifeCycle.contributeList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(version) + .append(status) + .append(contributeList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LimitConditions.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LimitConditions.java new file mode 100644 index 00000000..07a87366 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/LimitConditions.java @@ -0,0 +1,63 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.NonNegativeInteger; + +public class LimitConditions { + + // attributes + private NonNegativeInteger attemptLimit; // O + private String attemptAbsoluteDurationLimit; // O + + public LimitConditions() { + } + + public NonNegativeInteger getAttemptLimit() { + return attemptLimit; + } + + public void setAttemptLimit(NonNegativeInteger attemptLimit) { + this.attemptLimit = attemptLimit; + } + + public String getAttemptAbsoluteDurationLimit() { + return attemptAbsoluteDurationLimit; + } + + public void setAttemptAbsoluteDurationLimit(String attemptAbsoluteDurationLimit) { + this.attemptAbsoluteDurationLimit = attemptAbsoluteDurationLimit; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("attemptLimit", attemptLimit) + .append("attemptAbsoluteDurationLimit", attemptAbsoluteDurationLimit) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + LimitConditions that = (LimitConditions) o; + + return new EqualsBuilder() + .append(attemptLimit, that.attemptLimit) + .append(attemptAbsoluteDurationLimit, that.attemptAbsoluteDurationLimit) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(attemptLimit) + .append(attemptAbsoluteDurationLimit) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Manifest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Manifest.java new file mode 100644 index 00000000..930f7eda --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Manifest.java @@ -0,0 +1,133 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; +import com.xboe.module.scorm.cam.model.datatype.ID; + +/** + * A manifest is an XML document that contains a + * structured inventory of the content of a package. + * In this version, ADL recommends not to use (sub)manifests. + */ + +public class Manifest { + + // attributes + private ID identifier; // M + private String version; // O + private AnyURI xmlBase; // O + + // elements + private ManifestMetadata metadata; // 1...1 + private Organizations organizations; // 1...1 + private Resources resources; // 1...1 + private SequencingCollection sequencingCollection; // 0...1 +// private List<Manifest> subManifests; // don't implementation + + public Manifest() { + } + + public ID getIdentifier() { + return identifier; + } + + public void setIdentifier(ID identifier) { + this.identifier = identifier; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public AnyURI getXmlBase() { + return xmlBase; + } + + public void setXmlBase(AnyURI xmlBase) { + this.xmlBase = xmlBase; + } + + public ManifestMetadata getMetadata() { + return metadata; + } + + public void setMetadata(ManifestMetadata metadata) { + this.metadata = metadata; + } + + public Organizations getOrganizations() { + return organizations; + } + + public void setOrganizations(Organizations organizations) { + this.organizations = organizations; + } + + public Resources getResources() { + return resources; + } + + public void setResources(Resources resources) { + this.resources = resources; + } + + public SequencingCollection getSequencingCollection() { + return sequencingCollection; + } + + public void setSequencingCollection(SequencingCollection sequencingCollection) { + this.sequencingCollection = sequencingCollection; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifier", identifier) + .append("version", version) + .append("xmlBase", xmlBase) + .append("metadata", metadata) + .append("organizations", organizations) + .append("resources", resources) + .append("sequencingCollection", sequencingCollection) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Manifest manifest = (Manifest) o; + + return new EqualsBuilder() + .append(identifier, manifest.identifier) + .append(version, manifest.version) + .append(xmlBase, manifest.xmlBase) + .append(metadata, manifest.metadata) + .append(organizations, manifest.organizations) + .append(resources, manifest.resources) + .append(sequencingCollection, manifest.sequencingCollection) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifier) + .append(version) + .append(xmlBase) + .append(metadata) + .append(organizations) + .append(resources) + .append(sequencingCollection) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ManifestMetadata.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ManifestMetadata.java new file mode 100644 index 00000000..06a431b1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/ManifestMetadata.java @@ -0,0 +1,75 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +/** + * Metadata: Data describing the content package as a whole. + */ +public class ManifestMetadata { + + private String schema; // 1...1 {ADL SCORM} + private String schemaVersion; // 1...1 {2004 4th Edition} + private Metadata metadata; // 0...n + + public ManifestMetadata() { + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public String getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("schema", schema) + .append("schemaVersion", schemaVersion) + .append("metadata", metadata) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ManifestMetadata that = (ManifestMetadata) o; + + return new EqualsBuilder() + .append(schema, that.schema) + .append(schemaVersion, that.schemaVersion) + .append(metadata, that.metadata) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(schema) + .append(schemaVersion) + .append(metadata) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Map.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Map.java new file mode 100644 index 00000000..68342d8e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Map.java @@ -0,0 +1,77 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; + +public class Map { + + // attributes + private AnyURI targetID; // M + private boolean readSharedData; // O true + private boolean writeSharedData; // O true + + public Map() { + readSharedData = true; + writeSharedData = true; + } + + public AnyURI getTargetID() { + return targetID; + } + + public void setTargetID(AnyURI targetID) { + this.targetID = targetID; + } + + public boolean isReadSharedData() { + return readSharedData; + } + + public void setReadSharedData(boolean readSharedData) { + this.readSharedData = readSharedData; + } + + public boolean isWriteSharedData() { + return writeSharedData; + } + + public void setWriteSharedData(boolean writeSharedData) { + this.writeSharedData = writeSharedData; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("targetID", targetID) + .append("readSharedData", readSharedData) + .append("writeSharedData", writeSharedData) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Map map = (Map) o; + + return new EqualsBuilder() + .append(readSharedData, map.readSharedData) + .append(writeSharedData, map.writeSharedData) + .append(targetID, map.targetID) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(targetID) + .append(readSharedData) + .append(writeSharedData) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/MapInfo.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/MapInfo.java new file mode 100644 index 00000000..63baf26f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/MapInfo.java @@ -0,0 +1,103 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; + +public class MapInfo { + + // attributes + private AnyURI targetObjectiveID; // M + private boolean readSatisfiedStatus; // O true + private boolean readNormalizedMeasure; // O true + private boolean writeSatisfiedStatus; // O false + private boolean writeNormalizedMeasure; // O false + + public MapInfo() { + readSatisfiedStatus = true; + readNormalizedMeasure = true; + writeSatisfiedStatus = false; + writeNormalizedMeasure = false; + } + + public AnyURI getTargetObjectiveID() { + return targetObjectiveID; + } + + public void setTargetObjectiveID(AnyURI targetObjectiveID) { + this.targetObjectiveID = targetObjectiveID; + } + + public boolean isReadSatisfiedStatus() { + return readSatisfiedStatus; + } + + public void setReadSatisfiedStatus(boolean readSatisfiedStatus) { + this.readSatisfiedStatus = readSatisfiedStatus; + } + + public boolean isReadNormalizedMeasure() { + return readNormalizedMeasure; + } + + public void setReadNormalizedMeasure(boolean readNormalizedMeasure) { + this.readNormalizedMeasure = readNormalizedMeasure; + } + + public boolean isWriteSatisfiedStatus() { + return writeSatisfiedStatus; + } + + public void setWriteSatisfiedStatus(boolean writeSatisfiedStatus) { + this.writeSatisfiedStatus = writeSatisfiedStatus; + } + + public boolean isWriteNormalizedMeasure() { + return writeNormalizedMeasure; + } + + public void setWriteNormalizedMeasure(boolean writeNormalizedMeasure) { + this.writeNormalizedMeasure = writeNormalizedMeasure; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("targetObjectiveID", targetObjectiveID) + .append("readSatisfiedStatus", readSatisfiedStatus) + .append("readNormalizedMeasure", readNormalizedMeasure) + .append("writeSatisfiedStatus", writeSatisfiedStatus) + .append("writeNormalizedMeasure", writeNormalizedMeasure) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + MapInfo mapInfo = (MapInfo) o; + + return new EqualsBuilder() + .append(readSatisfiedStatus, mapInfo.readSatisfiedStatus) + .append(readNormalizedMeasure, mapInfo.readNormalizedMeasure) + .append(writeSatisfiedStatus, mapInfo.writeSatisfiedStatus) + .append(writeNormalizedMeasure, mapInfo.writeNormalizedMeasure) + .append(targetObjectiveID, mapInfo.targetObjectiveID) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(targetObjectiveID) + .append(readSatisfiedStatus) + .append(readNormalizedMeasure) + .append(writeSatisfiedStatus) + .append(writeNormalizedMeasure) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/MetaMetadata.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/MetaMetadata.java new file mode 100644 index 00000000..a923c6f2 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/MetaMetadata.java @@ -0,0 +1,90 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class MetaMetadata { + + private List<Identifier> identifierList; // 0...n + private List<Contribute> contributeList; // 0...n + private List<String> metadataSchema; // 0...n + private String language; // 0...1 + + public MetaMetadata() { + identifierList = new ArrayList<>(); + contributeList = new ArrayList<>(); + metadataSchema = new ArrayList<>(); + } + + public List<Identifier> getIdentifierList() { + return identifierList; + } + + public void setIdentifierList(List<Identifier> identifierList) { + this.identifierList = identifierList; + } + + public List<Contribute> getContributeList() { + return contributeList; + } + + public void setContributeList(List<Contribute> contributeList) { + this.contributeList = contributeList; + } + + public List<String> getMetadataSchema() { + return metadataSchema; + } + + public void setMetadataSchema(List<String> metadataSchema) { + this.metadataSchema = metadataSchema; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifierList", identifierList) + .append("contributeList", contributeList) + .append("metadataSchema", metadataSchema) + .append("language", language) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + MetaMetadata that = (MetaMetadata) o; + + return new EqualsBuilder() + .append(identifierList, that.identifierList) + .append(contributeList, that.contributeList) + .append(metadataSchema, that.metadataSchema) + .append(language, that.language) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifierList) + .append(contributeList) + .append(metadataSchema) + .append(language) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Metadata.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Metadata.java new file mode 100644 index 00000000..5ac85181 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Metadata.java @@ -0,0 +1,67 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Metadata { + + private List<LOM> lomList; // 0...n + private Map<String, LOM> locationLomMap; // 0...n + + public Metadata() { + lomList = new ArrayList<>(); + locationLomMap = new HashMap<>(); + } + + public List<LOM> getLomList() { + return lomList; + } + + public void setLomList(List<LOM> lomList) { + this.lomList = lomList; + } + + public Map<String, LOM> getLocationLomMap() { + return locationLomMap; + } + + public void setLocationLomMap(Map<String, LOM> locationLomMap) { + this.locationLomMap = locationLomMap; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("lomList", lomList) + .append("locationLomMap", locationLomMap) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Metadata metadata = (Metadata) o; + + return new EqualsBuilder() + .append(lomList, metadata.lomList) + .append(locationLomMap, metadata.locationLomMap) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(lomList) + .append(locationLomMap) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/NavigationInterface.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/NavigationInterface.java new file mode 100644 index 00000000..2d2129d5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/NavigationInterface.java @@ -0,0 +1,53 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class NavigationInterface { + + // elements + private List<HideLMSUI> hideLMSUIList; // 0...n + + public NavigationInterface() { + hideLMSUIList = new ArrayList<>(); + } + + public List<HideLMSUI> getHideLMSUIList() { + return hideLMSUIList; + } + + public void setHideLMSUIList(List<HideLMSUI> hideLMSUIList) { + this.hideLMSUIList = hideLMSUIList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("hideLMSUIList", hideLMSUIList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + NavigationInterface that = (NavigationInterface) o; + + return new EqualsBuilder() + .append(hideLMSUIList, that.hideLMSUIList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(hideLMSUIList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Objective.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Objective.java new file mode 100644 index 00000000..fa75c47c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Objective.java @@ -0,0 +1,96 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; +import com.xboe.module.scorm.cam.model.datatype.Decimal; + +public class Objective { + + // attributes + private boolean satisfiedByMeasure; // O false + private AnyURI objectiveID; // O(for primaryObjective) M(for Objective) + + // elements + private Decimal minNormalizedMeasure; // 0...1 1.0 [-1.0000,1.0000] + private List<MapInfo> mapInfoList; // 0...n + + public Objective() { + satisfiedByMeasure = false; + minNormalizedMeasure = new Decimal("1.0", 4); + mapInfoList = new ArrayList<>(); + } + + public boolean isSatisfiedByMeasure() { + return satisfiedByMeasure; + } + + public void setSatisfiedByMeasure(boolean satisfiedByMeasure) { + this.satisfiedByMeasure = satisfiedByMeasure; + } + + public AnyURI getObjectiveID() { + return objectiveID; + } + + public void setObjectiveID(AnyURI objectiveID) { + this.objectiveID = objectiveID; + } + + public Decimal getMinNormalizedMeasure() { + return minNormalizedMeasure; + } + + public void setMinNormalizedMeasure(Decimal minNormalizedMeasure) { + this.minNormalizedMeasure = minNormalizedMeasure; + } + + public List<MapInfo> getMapInfoList() { + return mapInfoList; + } + + public void setMapInfoList(List<MapInfo> mapInfoList) { + this.mapInfoList = mapInfoList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("satisfiedByMeasure", satisfiedByMeasure) + .append("objectiveID", objectiveID) + .append("minNormalizedMeasure", minNormalizedMeasure) + .append("mapInfoList", mapInfoList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Objective objective = (Objective) o; + + return new EqualsBuilder() + .append(satisfiedByMeasure, objective.satisfiedByMeasure) + .append(objectiveID, objective.objectiveID) + .append(minNormalizedMeasure, objective.minNormalizedMeasure) + .append(mapInfoList, objective.mapInfoList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(satisfiedByMeasure) + .append(objectiveID) + .append(minNormalizedMeasure) + .append(mapInfoList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Objectives.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Objectives.java new file mode 100644 index 00000000..1cea3bf2 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Objectives.java @@ -0,0 +1,65 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Objectives { + + // elements + private Objective primaryObjective; // 1...1 + private List<Objective> objectiveList; // 0...n + + public Objectives() { + objectiveList = new ArrayList<>(); + } + + public Objective getPrimaryObjective() { + return primaryObjective; + } + + public void setPrimaryObjective(Objective primaryObjective) { + this.primaryObjective = primaryObjective; + } + + public List<Objective> getObjectiveList() { + return objectiveList; + } + + public void setObjectiveList(List<Objective> objectiveList) { + this.objectiveList = objectiveList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("primaryObjective", primaryObjective) + .append("objectiveList", objectiveList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Objectives that = (Objectives) o; + + return new EqualsBuilder() + .append(primaryObjective, that.primaryObjective) + .append(objectiveList, that.objectiveList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(primaryObjective) + .append(objectiveList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/OrComposite.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/OrComposite.java new file mode 100644 index 00000000..45fc1bc1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/OrComposite.java @@ -0,0 +1,84 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class OrComposite { + + private Vocabulary type; // 0...1 + private Vocabulary name; // 0...1 + private String minimumVersion; // 0...1 + private String maximumVersion; // 0...1 + + public OrComposite() { + } + + public Vocabulary getType() { + return type; + } + + public void setType(Vocabulary type) { + this.type = type; + } + + public Vocabulary getName() { + return name; + } + + public void setName(Vocabulary name) { + this.name = name; + } + + public String getMinimumVersion() { + return minimumVersion; + } + + public void setMinimumVersion(String minimumVersion) { + this.minimumVersion = minimumVersion; + } + + public String getMaximumVersion() { + return maximumVersion; + } + + public void setMaximumVersion(String maximumVersion) { + this.maximumVersion = maximumVersion; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("type", type) + .append("name", name) + .append("minimumVersion", minimumVersion) + .append("maximumVersion", maximumVersion) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + OrComposite that = (OrComposite) o; + + return new EqualsBuilder() + .append(type, that.type) + .append(name, that.name) + .append(minimumVersion, that.minimumVersion) + .append(maximumVersion, that.maximumVersion) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(type) + .append(name) + .append(minimumVersion) + .append(maximumVersion) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Organization.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Organization.java new file mode 100644 index 00000000..d5a15a1d --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Organization.java @@ -0,0 +1,156 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.ID; + +public class Organization { + + // attributes + private ID identifier; // M + private String structure; // O hierarchical + private boolean objectivesGlobalToSystem; // O true + private boolean sharedDataGlobalToSystem; // O true + + // elements + private String title; // 1...1 + private List<Item> itemList; // 1...n + private Metadata metadata; // 0..1 + private CompletionThreshold completionThreshold; // 0...1 + private Sequencing sequencing; // 0...1 + + public Organization() { + structure = "hierarchical"; + objectivesGlobalToSystem = true; + sharedDataGlobalToSystem = true; + itemList = new ArrayList<>(); + } + + public ID getIdentifier() { + return identifier; + } + + public void setIdentifier(ID identifier) { + this.identifier = identifier; + } + + public String getStructure() { + return structure; + } + + public void setStructure(String structure) { + this.structure = structure; + } + + public boolean isObjectivesGlobalToSystem() { + return objectivesGlobalToSystem; + } + + public void setObjectivesGlobalToSystem(boolean objectivesGlobalToSystem) { + this.objectivesGlobalToSystem = objectivesGlobalToSystem; + } + + public boolean isSharedDataGlobalToSystem() { + return sharedDataGlobalToSystem; + } + + public void setSharedDataGlobalToSystem(boolean sharedDataGlobalToSystem) { + this.sharedDataGlobalToSystem = sharedDataGlobalToSystem; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List<Item> getItemList() { + return itemList; + } + + public void setItemList(List<Item> itemList) { + this.itemList = itemList; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + public CompletionThreshold getCompletionThreshold() { + return completionThreshold; + } + + public void setCompletionThreshold(CompletionThreshold completionThreshold) { + this.completionThreshold = completionThreshold; + } + + public Sequencing getSequencing() { + return sequencing; + } + + public void setSequencing(Sequencing sequencing) { + this.sequencing = sequencing; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifier", identifier) + .append("structure", structure) + .append("objectivesGlobalToSystem", objectivesGlobalToSystem) + .append("sharedDataGlobalToSystem", sharedDataGlobalToSystem) + .append("title", title) + .append("itemList", itemList) + .append("metadata", metadata) + .append("completionThreshold", completionThreshold) + .append("sequencing", sequencing) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Organization that = (Organization) o; + + return new EqualsBuilder() + .append(objectivesGlobalToSystem, that.objectivesGlobalToSystem) + .append(sharedDataGlobalToSystem, that.sharedDataGlobalToSystem) + .append(identifier, that.identifier) + .append(structure, that.structure) + .append(title, that.title) + .append(itemList, that.itemList) + .append(metadata, that.metadata) + .append(completionThreshold, that.completionThreshold) + .append(sequencing, that.sequencing) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifier) + .append(structure) + .append(objectivesGlobalToSystem) + .append(sharedDataGlobalToSystem) + .append(title) + .append(itemList) + .append(metadata) + .append(completionThreshold) + .append(sequencing) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Organizations.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Organizations.java new file mode 100644 index 00000000..bf19d971 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Organizations.java @@ -0,0 +1,75 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.IDRef; + +/** + * Organizations: Contains the content structure or organization + * of the learning resources making up a stand-alone unit or + * units of instruction. A definition of sequencing intent can + * be associated with the content structure. + */ +public class Organizations { + + // attributes + private IDRef defaultOrganizationID; // M + + // elements + private List<Organization> organizationList; // 0...n + + public Organizations() { + organizationList = new ArrayList<>(); + } + + public IDRef getDefaultOrganizationID() { + return defaultOrganizationID; + } + + public void setDefaultOrganizationID(IDRef defaultOrganizationID) { + this.defaultOrganizationID = defaultOrganizationID; + } + + public List<Organization> getOrganizationList() { + return organizationList; + } + + public void setOrganizationList(List<Organization> organizationList) { + this.organizationList = organizationList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("defaultOrganizationID", defaultOrganizationID) + .append("organizationList", organizationList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Organizations that = (Organizations) o; + + return new EqualsBuilder() + .append(defaultOrganizationID, that.defaultOrganizationID) + .append(organizationList, that.organizationList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(defaultOrganizationID) + .append(organizationList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Presentation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Presentation.java new file mode 100644 index 00000000..d298d0a9 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Presentation.java @@ -0,0 +1,48 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Presentation { + + private NavigationInterface navigationInterface; // 0...1 + + public Presentation() { + } + + public NavigationInterface getNavigationInterface() { + return navigationInterface; + } + + public void setNavigationInterface(NavigationInterface navigationInterface) { + this.navigationInterface = navigationInterface; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("navigationInterface", navigationInterface) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Presentation that = (Presentation) o; + + return new EqualsBuilder() + .append(navigationInterface, that.navigationInterface) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(navigationInterface) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RandomizationControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RandomizationControls.java new file mode 100644 index 00000000..dc700332 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RandomizationControls.java @@ -0,0 +1,91 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.NonNegativeInteger; +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RandomizationControls { + + // attributes + private Token randomizationTiming; // O never + private NonNegativeInteger selectCount; // O + private boolean reorderChildren; // O false + private Token selectionTiming; // O never + + public RandomizationControls() { + randomizationTiming = new Token("never"); + reorderChildren = false; + selectionTiming = new Token("never"); + } + + public Token getRandomizationTiming() { + return randomizationTiming; + } + + public void setRandomizationTiming(Token randomizationTiming) { + this.randomizationTiming = randomizationTiming; + } + + public NonNegativeInteger getSelectCount() { + return selectCount; + } + + public void setSelectCount(NonNegativeInteger selectCount) { + this.selectCount = selectCount; + } + + public boolean isReorderChildren() { + return reorderChildren; + } + + public void setReorderChildren(boolean reorderChildren) { + this.reorderChildren = reorderChildren; + } + + public Token getSelectionTiming() { + return selectionTiming; + } + + public void setSelectionTiming(Token selectionTiming) { + this.selectionTiming = selectionTiming; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("randomizationTiming", randomizationTiming) + .append("selectCount", selectCount) + .append("reorderChildren", reorderChildren) + .append("selectionTiming", selectionTiming) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RandomizationControls that = (RandomizationControls) o; + + return new EqualsBuilder() + .append(reorderChildren, that.reorderChildren) + .append(randomizationTiming, that.randomizationTiming) + .append(selectCount, that.selectCount) + .append(selectionTiming, that.selectionTiming) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(randomizationTiming) + .append(selectCount) + .append(reorderChildren) + .append(selectionTiming) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Relation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Relation.java new file mode 100644 index 00000000..0f422cc0 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Relation.java @@ -0,0 +1,60 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Relation { + + private Vocabulary kind; // 0...1 + private RelationResource resource; // 0...1 + + public Relation() { + } + + public Vocabulary getKind() { + return kind; + } + + public void setKind(Vocabulary kind) { + this.kind = kind; + } + + public RelationResource getResource() { + return resource; + } + + public void setResource(RelationResource resource) { + this.resource = resource; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("kind", kind) + .append("resource", resource) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Relation relation = (Relation) o; + + return new EqualsBuilder() + .append(kind, relation.kind) + .append(resource, relation.resource) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(kind) + .append(resource) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RelationResource.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RelationResource.java new file mode 100644 index 00000000..9c6e480e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RelationResource.java @@ -0,0 +1,65 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class RelationResource { + + private List<Identifier> identifierList; // 0...n + private List<LanguageStrings> descriptionList; // 0...n + + public RelationResource() { + identifierList = new ArrayList<>(); + descriptionList = new ArrayList<>(); + } + + public List<Identifier> getIdentifierList() { + return identifierList; + } + + public void setIdentifierList(List<Identifier> identifierList) { + this.identifierList = identifierList; + } + + public List<LanguageStrings> getDescriptionList() { + return descriptionList; + } + + public void setDescriptionList(List<LanguageStrings> descriptionList) { + this.descriptionList = descriptionList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifierList", identifierList) + .append("descriptionList", descriptionList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RelationResource that = (RelationResource) o; + + return new EqualsBuilder() + .append(identifierList, that.identifierList) + .append(descriptionList, that.descriptionList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifierList) + .append(descriptionList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Requirement.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Requirement.java new file mode 100644 index 00000000..7e1cfa60 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Requirement.java @@ -0,0 +1,52 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Requirement { + + private List<OrComposite> orCompositeList; // 0...n + + public Requirement() { + orCompositeList = new ArrayList<>(); + } + + public List<OrComposite> getOrCompositeList() { + return orCompositeList; + } + + public void setOrCompositeList(List<OrComposite> orCompositeList) { + this.orCompositeList = orCompositeList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("orCompositeList", orCompositeList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Requirement that = (Requirement) o; + + return new EqualsBuilder() + .append(orCompositeList, that.orCompositeList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(orCompositeList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Resource.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Resource.java new file mode 100644 index 00000000..8db04286 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Resource.java @@ -0,0 +1,149 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; +import com.xboe.module.scorm.cam.model.datatype.ID; + +/** + * If an <item> references a <resource>, then the <resource> element shall meet the following requirements: + * type=webcontent + * scormType=sco or asset + * href is required + */ +public class Resource { + + // attributes + private ID identifier; // M + private String type; // M + private String scormType; // M {sco}{asset} + private String href; // O + private AnyURI xmlBase; // O + + // elements + private Metadata metadata; // 0...1 + private List<File> fileList; // 0...1 + private List<Dependency> dependencyList; // 0...n + + public Resource() { + fileList = new ArrayList<>(); + dependencyList = new ArrayList<>(); + } + + public ID getIdentifier() { + return identifier; + } + + public void setIdentifier(ID identifier) { + this.identifier = identifier; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getScormType() { + return scormType; + } + + public void setScormType(String scormType) { + this.scormType = scormType; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public AnyURI getXmlBase() { + return xmlBase; + } + + public void setXmlBase(AnyURI xmlBase) { + this.xmlBase = xmlBase; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + public List<File> getFileList() { + return fileList; + } + + public void setFileList(List<File> fileList) { + this.fileList = fileList; + } + + public List<Dependency> getDependencyList() { + return dependencyList; + } + + public void setDependencyList(List<Dependency> dependencyList) { + this.dependencyList = dependencyList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifier", identifier) + .append("type", type) + .append("scormType", scormType) + .append("href", href) + .append("xmlBase", xmlBase) + .append("metadata", metadata) + .append("fileList", fileList) + .append("dependencyList", dependencyList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Resource resource = (Resource) o; + + return new EqualsBuilder() + .append(identifier, resource.identifier) + .append(type, resource.type) + .append(scormType, resource.scormType) + .append(href, resource.href) + .append(xmlBase, resource.xmlBase) + .append(metadata, resource.metadata) + .append(fileList, resource.fileList) + .append(dependencyList, resource.dependencyList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifier) + .append(type) + .append(scormType) + .append(href) + .append(xmlBase) + .append(metadata) + .append(fileList) + .append(dependencyList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Resources.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Resources.java new file mode 100644 index 00000000..6dc6f893 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Resources.java @@ -0,0 +1,72 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.AnyURI; + +/** + * Defines the learning resources bundled in the content package. + */ +public class Resources { + + // attributes + private AnyURI xmlBase; // O + + // elements + private List<Resource> resourceList; // 0...n + + public Resources() { + resourceList = new ArrayList<>(); + } + + public AnyURI getXmlBase() { + return xmlBase; + } + + public void setXmlBase(AnyURI xmlBase) { + this.xmlBase = xmlBase; + } + + public List<Resource> getResourceList() { + return resourceList; + } + + public void setResourceList(List<Resource> resourceList) { + this.resourceList = resourceList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("xmlBase", xmlBase) + .append("resourceList", resourceList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Resources resources = (Resources) o; + + return new EqualsBuilder() + .append(xmlBase, resources.xmlBase) + .append(resourceList, resources.resourceList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(xmlBase) + .append(resourceList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Rights.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Rights.java new file mode 100644 index 00000000..9da9fb46 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Rights.java @@ -0,0 +1,72 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Rights { + + private Vocabulary cost; // 0...1 + private Vocabulary copyrightAndOtherRestrictions; // 0...1 + private LanguageStrings description; // 0...1 + + public Rights() { + } + + public Vocabulary getCost() { + return cost; + } + + public void setCost(Vocabulary cost) { + this.cost = cost; + } + + public Vocabulary getCopyrightAndOtherRestrictions() { + return copyrightAndOtherRestrictions; + } + + public void setCopyrightAndOtherRestrictions(Vocabulary copyrightAndOtherRestrictions) { + this.copyrightAndOtherRestrictions = copyrightAndOtherRestrictions; + } + + public LanguageStrings getDescription() { + return description; + } + + public void setDescription(LanguageStrings description) { + this.description = description; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("cost", cost) + .append("copyrightAndOtherRestrictions", copyrightAndOtherRestrictions) + .append("description", description) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Rights rights = (Rights) o; + + return new EqualsBuilder() + .append(cost, rights.cost) + .append(copyrightAndOtherRestrictions, rights.copyrightAndOtherRestrictions) + .append(description, rights.description) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(cost) + .append(copyrightAndOtherRestrictions) + .append(description) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupCondition.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupCondition.java new file mode 100644 index 00000000..e3ade47a --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupCondition.java @@ -0,0 +1,64 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RollupCondition { + + // attributes + private Token condition; // M + private Token operator; // O noOp + + public RollupCondition() { + operator = new Token("noOp"); + } + + public Token getCondition() { + return condition; + } + + public void setCondition(Token condition) { + this.condition = condition; + } + + public Token getOperator() { + return operator; + } + + public void setOperator(Token operator) { + this.operator = operator; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("condition", condition) + .append("operator", operator) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RollupCondition that = (RollupCondition) o; + + return new EqualsBuilder() + .append(condition, that.condition) + .append(operator, that.operator) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(condition) + .append(operator) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupConditions.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupConditions.java new file mode 100644 index 00000000..0016d8bd --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupConditions.java @@ -0,0 +1,70 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RollupConditions { + + // attributes + private Token conditionCombination; // O all + + // elements + private List<RollupCondition> rollupConditionList; // 1...n + + public RollupConditions() { + conditionCombination = new Token("all"); + rollupConditionList = new ArrayList<>(); + } + + public Token getConditionCombination() { + return conditionCombination; + } + + public void setConditionCombination(Token conditionCombination) { + this.conditionCombination = conditionCombination; + } + + public List<RollupCondition> getRollupConditionList() { + return rollupConditionList; + } + + public void setRollupConditionList(List<RollupCondition> rollupConditionList) { + this.rollupConditionList = rollupConditionList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("conditionCombination", conditionCombination) + .append("rollupConditionList", rollupConditionList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RollupConditions that = (RollupConditions) o; + + return new EqualsBuilder() + .append(conditionCombination, that.conditionCombination) + .append(rollupConditionList, that.rollupConditionList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(conditionCombination) + .append(rollupConditionList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupConsiderations.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupConsiderations.java new file mode 100644 index 00000000..03e73bc9 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupConsiderations.java @@ -0,0 +1,104 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RollupConsiderations { + + // attributes + private Token requiredForSatisfied; // O always + private Token requiredForNotSatisfied; // O always + private Token requiredForCompleted; // O always + private Token requiredForIncomplete; // O always + private boolean measureSatisfactionIfActive; // O true + + public RollupConsiderations() { + requiredForSatisfied = new Token("always"); + requiredForNotSatisfied = new Token("always"); + requiredForCompleted = new Token("always"); + requiredForIncomplete = new Token("always"); + measureSatisfactionIfActive = true; + } + + public Token getRequiredForSatisfied() { + return requiredForSatisfied; + } + + public void setRequiredForSatisfied(Token requiredForSatisfied) { + this.requiredForSatisfied = requiredForSatisfied; + } + + public Token getRequiredForNotSatisfied() { + return requiredForNotSatisfied; + } + + public void setRequiredForNotSatisfied(Token requiredForNotSatisfied) { + this.requiredForNotSatisfied = requiredForNotSatisfied; + } + + public Token getRequiredForCompleted() { + return requiredForCompleted; + } + + public void setRequiredForCompleted(Token requiredForCompleted) { + this.requiredForCompleted = requiredForCompleted; + } + + public Token getRequiredForIncomplete() { + return requiredForIncomplete; + } + + public void setRequiredForIncomplete(Token requiredForIncomplete) { + this.requiredForIncomplete = requiredForIncomplete; + } + + public boolean isMeasureSatisfactionIfActive() { + return measureSatisfactionIfActive; + } + + public void setMeasureSatisfactionIfActive(boolean measureSatisfactionIfActive) { + this.measureSatisfactionIfActive = measureSatisfactionIfActive; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("requiredForSatisfied", requiredForSatisfied) + .append("requiredForNotSatisfied", requiredForNotSatisfied) + .append("requiredForCompleted", requiredForCompleted) + .append("requiredForIncomplete", requiredForIncomplete) + .append("measureSatisfactionIfActive", measureSatisfactionIfActive) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RollupConsiderations that = (RollupConsiderations) o; + + return new EqualsBuilder() + .append(measureSatisfactionIfActive, that.measureSatisfactionIfActive) + .append(requiredForSatisfied, that.requiredForSatisfied) + .append(requiredForNotSatisfied, that.requiredForNotSatisfied) + .append(requiredForCompleted, that.requiredForCompleted) + .append(requiredForIncomplete, that.requiredForIncomplete) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(requiredForSatisfied) + .append(requiredForNotSatisfied) + .append(requiredForCompleted) + .append(requiredForIncomplete) + .append(measureSatisfactionIfActive) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupRule.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupRule.java new file mode 100644 index 00000000..871eeb41 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupRule.java @@ -0,0 +1,106 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Decimal; +import com.xboe.module.scorm.cam.model.datatype.NonNegativeInteger; +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RollupRule { + + // attributes + private Token childActivitySet; // O all + private NonNegativeInteger minimumCount; // O 0 + private Decimal minimumPercent; // O 0.0000 [0.0000,1.0000] + + // elements + private RollupConditions rollupConditions; // 1...1 + private Token rollupAction; // 1...1 + + public RollupRule() { + childActivitySet = new Token("all"); + minimumCount = new NonNegativeInteger("0"); + minimumPercent = new Decimal("0.0000", 4); + } + + public Token getChildActivitySet() { + return childActivitySet; + } + + public void setChildActivitySet(Token childActivitySet) { + this.childActivitySet = childActivitySet; + } + + public NonNegativeInteger getMinimumCount() { + return minimumCount; + } + + public void setMinimumCount(NonNegativeInteger minimumCount) { + this.minimumCount = minimumCount; + } + + public Decimal getMinimumPercent() { + return minimumPercent; + } + + public void setMinimumPercent(Decimal minimumPercent) { + this.minimumPercent = minimumPercent; + } + + public RollupConditions getRollupConditions() { + return rollupConditions; + } + + public void setRollupConditions(RollupConditions rollupConditions) { + this.rollupConditions = rollupConditions; + } + + public Token getRollupAction() { + return rollupAction; + } + + public void setRollupAction(Token rollupAction) { + this.rollupAction = rollupAction; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("childActivitySet", childActivitySet) + .append("minimumCount", minimumCount) + .append("minimumPercent", minimumPercent) + .append("rollupConditions", rollupConditions) + .append("rollupAction", rollupAction) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RollupRule that = (RollupRule) o; + + return new EqualsBuilder() + .append(childActivitySet, that.childActivitySet) + .append(minimumCount, that.minimumCount) + .append(minimumPercent, that.minimumPercent) + .append(rollupConditions, that.rollupConditions) + .append(rollupAction, that.rollupAction) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(childActivitySet) + .append(minimumCount) + .append(minimumPercent) + .append(rollupConditions) + .append(rollupAction) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupRules.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupRules.java new file mode 100644 index 00000000..2d9e6141 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RollupRules.java @@ -0,0 +1,96 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Decimal; + +public class RollupRules { + + // attributes + private boolean rollupObjectiveSatisfied; // O true + private boolean rollupProgressCompletion; // O true + private Decimal objectiveMeasureWeight; // O 1.0000 [0.0000,1.0000] + + // elements + private List<RollupRule> rollupRuleList; // 0...n + + public RollupRules() { + rollupObjectiveSatisfied = true; + rollupProgressCompletion = true; + objectiveMeasureWeight = new Decimal("1.0000", 4); + rollupRuleList = new ArrayList<>(); + } + + public boolean isRollupObjectiveSatisfied() { + return rollupObjectiveSatisfied; + } + + public void setRollupObjectiveSatisfied(boolean rollupObjectiveSatisfied) { + this.rollupObjectiveSatisfied = rollupObjectiveSatisfied; + } + + public boolean isRollupProgressCompletion() { + return rollupProgressCompletion; + } + + public void setRollupProgressCompletion(boolean rollupProgressCompletion) { + this.rollupProgressCompletion = rollupProgressCompletion; + } + + public Decimal getObjectiveMeasureWeight() { + return objectiveMeasureWeight; + } + + public void setObjectiveMeasureWeight(Decimal objectiveMeasureWeight) { + this.objectiveMeasureWeight = objectiveMeasureWeight; + } + + public List<RollupRule> getRollupRuleList() { + return rollupRuleList; + } + + public void setRollupRuleList(List<RollupRule> rollupRuleList) { + this.rollupRuleList = rollupRuleList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("rollupObjectiveSatisfied", rollupObjectiveSatisfied) + .append("rollupProgressCompletion", rollupProgressCompletion) + .append("objectiveMeasureWeight", objectiveMeasureWeight) + .append("rollupRuleList", rollupRuleList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RollupRules that = (RollupRules) o; + + return new EqualsBuilder() + .append(rollupObjectiveSatisfied, that.rollupObjectiveSatisfied) + .append(rollupProgressCompletion, that.rollupProgressCompletion) + .append(objectiveMeasureWeight, that.objectiveMeasureWeight) + .append(rollupRuleList, that.rollupRuleList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(rollupObjectiveSatisfied) + .append(rollupProgressCompletion) + .append(objectiveMeasureWeight) + .append(rollupRuleList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleAction.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleAction.java new file mode 100644 index 00000000..0821e094 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleAction.java @@ -0,0 +1,50 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RuleAction { + + private Token action; // M + + public RuleAction() { + } + + public Token getAction() { + return action; + } + + public void setAction(Token action) { + this.action = action; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("action", action) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RuleAction that = (RuleAction) o; + + return new EqualsBuilder() + .append(action, that.action) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(action) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleCondition.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleCondition.java new file mode 100644 index 00000000..a52eb7c3 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleCondition.java @@ -0,0 +1,89 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Decimal; +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RuleCondition { + + // attributes + private Token condition; // M + private String referencedObjective; // O + private Decimal measureThreshold; // O [-1.0000,1.0000] + private Token operator; // O noOp + + public RuleCondition() { + operator = new Token("noOp"); + } + + public Token getCondition() { + return condition; + } + + public void setCondition(Token condition) { + this.condition = condition; + } + + public String getReferencedObjective() { + return referencedObjective; + } + + public void setReferencedObjective(String referencedObjective) { + this.referencedObjective = referencedObjective; + } + + public Decimal getMeasureThreshold() { + return measureThreshold; + } + + public void setMeasureThreshold(Decimal measureThreshold) { + this.measureThreshold = measureThreshold; + } + + public Token getOperator() { + return operator; + } + + public void setOperator(Token operator) { + this.operator = operator; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("condition", condition) + .append("referencedObjective", referencedObjective) + .append("measureThreshold", measureThreshold) + .append("operator", operator) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RuleCondition that = (RuleCondition) o; + + return new EqualsBuilder() + .append(condition, that.condition) + .append(referencedObjective, that.referencedObjective) + .append(measureThreshold, that.measureThreshold) + .append(operator, that.operator) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(condition) + .append(referencedObjective) + .append(measureThreshold) + .append(operator) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleConditions.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleConditions.java new file mode 100644 index 00000000..9bf77cd0 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/RuleConditions.java @@ -0,0 +1,70 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.Token; + +public class RuleConditions { + + // attributes + private Token conditionCombination; // O all + + // elements + private List<RuleCondition> ruleConditionList; // 1...n + + public RuleConditions() { + conditionCombination = new Token("all"); + ruleConditionList = new ArrayList<>(); + } + + public Token getConditionCombination() { + return conditionCombination; + } + + public void setConditionCombination(Token conditionCombination) { + this.conditionCombination = conditionCombination; + } + + public List<RuleCondition> getRuleConditionList() { + return ruleConditionList; + } + + public void setRuleConditionList(List<RuleCondition> ruleConditionList) { + this.ruleConditionList = ruleConditionList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("conditionCombination", conditionCombination) + .append("ruleConditionList", ruleConditionList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + RuleConditions that = (RuleConditions) o; + + return new EqualsBuilder() + .append(conditionCombination, that.conditionCombination) + .append(ruleConditionList, that.ruleConditionList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(conditionCombination) + .append(ruleConditionList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Sequencing.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Sequencing.java new file mode 100644 index 00000000..048e6f05 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Sequencing.java @@ -0,0 +1,187 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.xboe.module.scorm.cam.model.datatype.ID; +import com.xboe.module.scorm.cam.model.datatype.IDRef; + +public class Sequencing { + + // attributes + private ID id; // O + private IDRef idRef; // O + + // elements + private ControlMode controlMode; // 0...1 + private SequencingRules sequencingRules; // 0...1 + private LimitConditions limitConditions; // 0...1 + private RollupRules rollupRules; // 0...1 + private Objectives objectives; // 0...1 + private RandomizationControls randomizationControls; // 0...1 + private DeliveryControls deliveryControls; // // 0...1 + private ConstrainedChoiceConsiderations constrainedChoiceConsiderations; // 0...1 + private RollupConsiderations rollupConsiderations; // 0...1 + private AdlseqObjectives adlseqObjectives; // 0...1 +// private AuxiliaryResources auxiliaryResources; // don't implementation + + public Sequencing() { + } + + public ID getId() { + return id; + } + + public void setId(ID id) { + this.id = id; + } + + public IDRef getIdRef() { + return idRef; + } + + public void setIdRef(IDRef idRef) { + this.idRef = idRef; + } + + public ControlMode getControlMode() { + return controlMode; + } + + public void setControlMode(ControlMode controlMode) { + this.controlMode = controlMode; + } + + public SequencingRules getSequencingRules() { + return sequencingRules; + } + + public void setSequencingRules(SequencingRules sequencingRules) { + this.sequencingRules = sequencingRules; + } + + public LimitConditions getLimitConditions() { + return limitConditions; + } + + public void setLimitConditions(LimitConditions limitConditions) { + this.limitConditions = limitConditions; + } + + public RollupRules getRollupRules() { + return rollupRules; + } + + public void setRollupRules(RollupRules rollupRules) { + this.rollupRules = rollupRules; + } + + public Objectives getObjectives() { + return objectives; + } + + public void setObjectives(Objectives objectives) { + this.objectives = objectives; + } + + public RandomizationControls getRandomizationControls() { + return randomizationControls; + } + + public void setRandomizationControls(RandomizationControls randomizationControls) { + this.randomizationControls = randomizationControls; + } + + public DeliveryControls getDeliveryControls() { + return deliveryControls; + } + + public void setDeliveryControls(DeliveryControls deliveryControls) { + this.deliveryControls = deliveryControls; + } + + public ConstrainedChoiceConsiderations getConstrainedChoiceConsiderations() { + return constrainedChoiceConsiderations; + } + + public void setConstrainedChoiceConsiderations(ConstrainedChoiceConsiderations constrainedChoiceConsiderations) { + this.constrainedChoiceConsiderations = constrainedChoiceConsiderations; + } + + public RollupConsiderations getRollupConsiderations() { + return rollupConsiderations; + } + + public void setRollupConsiderations(RollupConsiderations rollupConsiderations) { + this.rollupConsiderations = rollupConsiderations; + } + + public AdlseqObjectives getAdlseqObjectives() { + return adlseqObjectives; + } + + public void setAdlseqObjectives(AdlseqObjectives adlseqObjectives) { + this.adlseqObjectives = adlseqObjectives; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("id", id) + .append("idRef", idRef) + .append("controlMode", controlMode) + .append("sequencingRules", sequencingRules) + .append("limitConditions", limitConditions) + .append("rollupRules", rollupRules) + .append("objectives", objectives) + .append("randomizationControls", randomizationControls) + .append("deliveryControls", deliveryControls) + .append("constrainedChoiceConsiderations", constrainedChoiceConsiderations) + .append("rollupConsiderations", rollupConsiderations) + .append("adlseqObjectives", adlseqObjectives) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Sequencing that = (Sequencing) o; + + return new EqualsBuilder() + .append(id, that.id) + .append(idRef, that.idRef) + .append(controlMode, that.controlMode) + .append(sequencingRules, that.sequencingRules) + .append(limitConditions, that.limitConditions) + .append(rollupRules, that.rollupRules) + .append(objectives, that.objectives) + .append(randomizationControls, that.randomizationControls) + .append(deliveryControls, that.deliveryControls) + .append(constrainedChoiceConsiderations, that.constrainedChoiceConsiderations) + .append(rollupConsiderations, that.rollupConsiderations) + .append(adlseqObjectives, that.adlseqObjectives) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(id) + .append(idRef) + .append(controlMode) + .append(sequencingRules) + .append(limitConditions) + .append(rollupRules) + .append(objectives) + .append(randomizationControls) + .append(deliveryControls) + .append(constrainedChoiceConsiderations) + .append(rollupConsiderations) + .append(adlseqObjectives) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/SequencingCollection.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/SequencingCollection.java new file mode 100644 index 00000000..64cf0137 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/SequencingCollection.java @@ -0,0 +1,52 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class SequencingCollection { + + private List<Sequencing> sequencingList; // 1...n + + public SequencingCollection() { + sequencingList = new ArrayList<>(); + } + + public List<Sequencing> getSequencingList() { + return sequencingList; + } + + public void setSequencingList(List<Sequencing> sequencingList) { + this.sequencingList = sequencingList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("sequencingList", sequencingList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + SequencingCollection that = (SequencingCollection) o; + + return new EqualsBuilder() + .append(sequencingList, that.sequencingList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(sequencingList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/SequencingRules.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/SequencingRules.java new file mode 100644 index 00000000..58637460 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/SequencingRules.java @@ -0,0 +1,79 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class SequencingRules { + + // elements + private List<ConditionRule> preConditionRuleList; // 0...n + private List<ConditionRule> exitConditionRuleList; // 0...n + private List<ConditionRule> postConditionRuleList; // 0...n + + public SequencingRules() { + preConditionRuleList = new ArrayList<>(); + exitConditionRuleList = new ArrayList<>(); + postConditionRuleList = new ArrayList<>(); + } + + public List<ConditionRule> getPreConditionRuleList() { + return preConditionRuleList; + } + + public void setPreConditionRuleList(List<ConditionRule> preConditionRuleList) { + this.preConditionRuleList = preConditionRuleList; + } + + public List<ConditionRule> getExitConditionRuleList() { + return exitConditionRuleList; + } + + public void setExitConditionRuleList(List<ConditionRule> exitConditionRuleList) { + this.exitConditionRuleList = exitConditionRuleList; + } + + public List<ConditionRule> getPostConditionRuleList() { + return postConditionRuleList; + } + + public void setPostConditionRuleList(List<ConditionRule> postConditionRuleList) { + this.postConditionRuleList = postConditionRuleList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("preConditionRuleList", preConditionRuleList) + .append("exitConditionRuleList", exitConditionRuleList) + .append("postConditionRuleList", postConditionRuleList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + SequencingRules that = (SequencingRules) o; + + return new EqualsBuilder() + .append(preConditionRuleList, that.preConditionRuleList) + .append(exitConditionRuleList, that.exitConditionRuleList) + .append(postConditionRuleList, that.postConditionRuleList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(preConditionRuleList) + .append(exitConditionRuleList) + .append(postConditionRuleList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Taxon.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Taxon.java new file mode 100644 index 00000000..740485f7 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Taxon.java @@ -0,0 +1,60 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Taxon { + + private String id; // 0...1 + private LanguageStrings entry; // 0...1 + + public Taxon() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LanguageStrings getEntry() { + return entry; + } + + public void setEntry(LanguageStrings entry) { + this.entry = entry; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("id", id) + .append("entry", entry) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Taxon taxon = (Taxon) o; + + return new EqualsBuilder() + .append(id, taxon.id) + .append(entry, taxon.entry) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(id) + .append(entry) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/TaxonPath.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/TaxonPath.java new file mode 100644 index 00000000..35485e36 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/TaxonPath.java @@ -0,0 +1,64 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class TaxonPath { + + private LanguageStrings source; // 0...1 + private List<Taxon> taxonList; // 0...n + + public TaxonPath() { + taxonList = new ArrayList<>(); + } + + public LanguageStrings getSource() { + return source; + } + + public void setSource(LanguageStrings source) { + this.source = source; + } + + public List<Taxon> getTaxonList() { + return taxonList; + } + + public void setTaxonList(List<Taxon> taxonList) { + this.taxonList = taxonList; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("source", source) + .append("taxonList", taxonList) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + TaxonPath taxonPath = (TaxonPath) o; + + return new EqualsBuilder() + .append(source, taxonPath.source) + .append(taxonList, taxonPath.taxonList) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(source) + .append(taxonList) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Technical.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Technical.java new file mode 100644 index 00000000..7b5c7ead --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Technical.java @@ -0,0 +1,126 @@ +package com.xboe.module.scorm.cam.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Technical { + + private List<String> formatList; // 0...n + private String size; // 0...1 + private List<String> locationList; // 0...n + private List<Requirement> requirementList; // 0...n + private LanguageStrings installationRemarks; // 0...1 + private LanguageStrings otherPlatformRequirements; // 0...1 + private Duration duration; // 0...1 + + public Technical() { + formatList = new ArrayList<>(); + locationList = new ArrayList<>(); + requirementList = new ArrayList<>(); + } + + public List<String> getFormatList() { + return formatList; + } + + public void setFormatList(List<String> formatList) { + this.formatList = formatList; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public List<String> getLocationList() { + return locationList; + } + + public void setLocationList(List<String> locationList) { + this.locationList = locationList; + } + + public List<Requirement> getRequirementList() { + return requirementList; + } + + public void setRequirementList(List<Requirement> requirementList) { + this.requirementList = requirementList; + } + + public LanguageStrings getInstallationRemarks() { + return installationRemarks; + } + + public void setInstallationRemarks(LanguageStrings installationRemarks) { + this.installationRemarks = installationRemarks; + } + + public LanguageStrings getOtherPlatformRequirements() { + return otherPlatformRequirements; + } + + public void setOtherPlatformRequirements(LanguageStrings otherPlatformRequirements) { + this.otherPlatformRequirements = otherPlatformRequirements; + } + + public Duration getDuration() { + return duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("formatList", formatList) + .append("size", size) + .append("locationList", locationList) + .append("requirementList", requirementList) + .append("installationRemarks", installationRemarks) + .append("otherPlatformRequirements", otherPlatformRequirements) + .append("duration", duration) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Technical technical = (Technical) o; + + return new EqualsBuilder() + .append(formatList, technical.formatList) + .append(size, technical.size) + .append(locationList, technical.locationList) + .append(requirementList, technical.requirementList) + .append(installationRemarks, technical.installationRemarks) + .append(otherPlatformRequirements, technical.otherPlatformRequirements) + .append(duration, technical.duration) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(formatList) + .append(size) + .append(locationList) + .append(requirementList) + .append(installationRemarks) + .append(otherPlatformRequirements) + .append(duration) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Vocabulary.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Vocabulary.java new file mode 100644 index 00000000..9ecabdb6 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/Vocabulary.java @@ -0,0 +1,62 @@ +package com.xboe.module.scorm.cam.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class Vocabulary { + + private String source; // 1...1 + private String value; // 1...1 + + public Vocabulary(String source, String value) { + this.source = source; + this.value = value; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("source", source) + .append("value", value) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Vocabulary that = (Vocabulary) o; + + return new EqualsBuilder() + .append(source, that.source) + .append(value, that.value) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(source) + .append(value) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/AnyURI.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/AnyURI.java new file mode 100644 index 00000000..8ef299aa --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/AnyURI.java @@ -0,0 +1,8 @@ +package com.xboe.module.scorm.cam.model.datatype; + +public class AnyURI extends XMLDataType { + + public AnyURI(String value) { + super(value); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/Decimal.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/Decimal.java new file mode 100644 index 00000000..12a810a8 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/Decimal.java @@ -0,0 +1,24 @@ +package com.xboe.module.scorm.cam.model.datatype; + +import java.math.BigDecimal; + +public class Decimal extends XMLDataType { + + private BigDecimal decimalValue; + private int scale; + + public Decimal(String value, int scale) { + super(value); + this.scale = scale; + decimalValue = BigDecimal.valueOf(Double.valueOf(value)) + .setScale(this.scale, BigDecimal.ROUND_HALF_UP); + } + + public BigDecimal getDecimalValue() { + return decimalValue; + } + + public int getScale() { + return scale; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/ID.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/ID.java new file mode 100644 index 00000000..0c8cf407 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/ID.java @@ -0,0 +1,8 @@ +package com.xboe.module.scorm.cam.model.datatype; + +public class ID extends XMLDataType { + + public ID(String value) { + super(value); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/IDRef.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/IDRef.java new file mode 100644 index 00000000..a61ff6b2 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/IDRef.java @@ -0,0 +1,9 @@ +package com.xboe.module.scorm.cam.model.datatype; + +public class IDRef extends XMLDataType { + + public IDRef(String value) { + super(value); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/NonNegativeInteger.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/NonNegativeInteger.java new file mode 100644 index 00000000..c08220a0 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/NonNegativeInteger.java @@ -0,0 +1,18 @@ +package com.xboe.module.scorm.cam.model.datatype; + +public class NonNegativeInteger extends XMLDataType { + + private int intValue; + + public NonNegativeInteger(String value) { + super(value); + this.intValue = Integer.parseInt(value); + if (this.intValue < 0) { + throw new IllegalArgumentException("value should be non-negative integer"); + } + } + + public int getIntValue() { + return intValue; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/Token.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/Token.java new file mode 100644 index 00000000..ca4fe953 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/Token.java @@ -0,0 +1,8 @@ +package com.xboe.module.scorm.cam.model.datatype; + +public class Token extends XMLDataType { + + public Token(String value) { + super(value); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/VCard.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/VCard.java new file mode 100644 index 00000000..65dcbf46 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/VCard.java @@ -0,0 +1,8 @@ +package com.xboe.module.scorm.cam.model.datatype; + +public class VCard extends XMLDataType { + + public VCard(String value) { + super(value); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/XMLDataType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/XMLDataType.java new file mode 100644 index 00000000..b00687be --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/datatype/XMLDataType.java @@ -0,0 +1,23 @@ +package com.xboe.module.scorm.cam.model.datatype; + +public abstract class XMLDataType { + + String value; + + public XMLDataType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/util/CPUtils.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/util/CPUtils.java new file mode 100644 index 00000000..5dfeed58 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/cam/model/util/CPUtils.java @@ -0,0 +1,129 @@ +package com.xboe.module.scorm.cam.model.util; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import com.xboe.module.scorm.cam.load.ModelUtils; +import com.xboe.module.scorm.cam.model.AdlseqMapInfo; +import com.xboe.module.scorm.cam.model.AdlseqObjective; +import com.xboe.module.scorm.cam.model.AdlseqObjectives; +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.Dependency; +import com.xboe.module.scorm.cam.model.Item; +import com.xboe.module.scorm.cam.model.MapInfo; +import com.xboe.module.scorm.cam.model.Objective; +import com.xboe.module.scorm.cam.model.Organization; +import com.xboe.module.scorm.cam.model.Resource; +import com.xboe.module.scorm.cam.model.Sequencing; +import com.xboe.module.scorm.cam.model.SequencingCollection; +import com.xboe.module.scorm.cam.model.datatype.AnyURI; + +public final class CPUtils { + + public static Resource findResource(ContentPackage contentPackage, String resourceID) { + if (contentPackage == null || contentPackage.getManifest() == null + || contentPackage.getManifest().getResources() == null || StringUtils.isBlank(resourceID)) { + return null; + } + for (Resource resource : contentPackage.getManifest().getResources().getResourceList()) { + if (resourceID.equals(resource.getIdentifier().getValue())) { + return resource; + } + } + return null; + } + + public static void findResource(ContentPackage contentPackage, Resource resource, List<Resource> resourceList) { + if (contentPackage == null || contentPackage.getManifest() == null + || contentPackage.getManifest().getResources() == null || resource == null) { + return; + } + for (Dependency dependency : resource.getDependencyList()) { + Resource res = findResource(contentPackage, dependency.getIdentifierref()); + if (res != null) { + resourceList.add(res); + } + findResource(contentPackage, res, resourceList); + } + } + + public static Item findItemByIdentifier(ContentPackage contentPackage, String identifier) { + if (contentPackage == null) { + return null; + } + for (Organization organization : contentPackage.getManifest().getOrganizations().getOrganizationList()) { + for (Item item : organization.getItemList()) { + Item i = findItemByIdentifier(item, identifier); + if (i != null) { + return i; + } + } + } + return null; + } + + private static Item findItemByIdentifier(Item item, String identifier) { + if (StringUtils.equals(identifier, item.getIdentifier().getValue())) { + return item; + } + for (Item i : item.getItemList()) { + Item it = findItemByIdentifier(i, identifier); + if (it != null) { + return it; + } + } + return null; + } + + public static AdlseqObjective findAdlseqObjectiveByID(AdlseqObjectives adlseqObjectives, AnyURI id) { + if (adlseqObjectives == null || ModelUtils.isAnyUriEmpty(id)) { + return null; + } + for (AdlseqObjective adlseqObjective : adlseqObjectives.getObjectiveList()) { + if (adlseqObjective.getObjectiveID() != null && id.getValue().equals(adlseqObjective.getObjectiveID().getValue())) { + return adlseqObjective; + } + } + return null; + } + + public static AdlseqMapInfo findAdlseqMapInfoByID(AdlseqObjective adlseqObjective, AnyURI targetObjectiveID) { + if (adlseqObjective == null || ModelUtils.isAnyUriEmpty(targetObjectiveID)) { + return null; + } + for (AdlseqMapInfo adlseqMapInfo : adlseqObjective.getMapInfoList()) { + if (adlseqMapInfo.getTargetObjectiveID().getValue().equals(targetObjectiveID.getValue())) { + return adlseqMapInfo; + } + } + return null; + } + + public static MapInfo findMapInfoByID(Objective objective, AnyURI targetObjectiveID) { + if (objective == null || ModelUtils.isAnyUriEmpty(targetObjectiveID)) { + return null; + } + for (MapInfo mapInfo : objective.getMapInfoList()) { + if (mapInfo.getTargetObjectiveID().getValue().equals(targetObjectiveID.getValue())) { + return mapInfo; + } + } + return null; + } + + public static Sequencing findSequencingByID(SequencingCollection sequencingCollection, String id) { + if (sequencingCollection == null) { + return null; + } + for (Sequencing sequencing : sequencingCollection.getSequencingList()) { + if (sequencing.getIdRef() != null) { + if (StringUtils.equals(id, sequencing.getIdRef().getValue())) { + return sequencing; + } + } + } + return null; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/CommonUtils.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/CommonUtils.java new file mode 100644 index 00000000..d41ecb72 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/CommonUtils.java @@ -0,0 +1,89 @@ +package com.xboe.module.scorm.common; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URI; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +public class CommonUtils { + + private static final Set<String> LANGUAGE_CODES = new HashSet<>(Arrays.asList(Locale.getISOLanguages())); + private static final Set<String> LANGUAGE_SUBCODES = new HashSet<>(Arrays.asList(Locale.getISOCountries())); + + static { + Set<String> iso3 = new HashSet<>(); + for (String languageCode : LANGUAGE_CODES) { + try { + iso3.add(new Locale(languageCode).getISO3Language()); + } catch (Exception ignored) { + + } + } + LANGUAGE_CODES.addAll(iso3); + } + + public static String stringifyError(Throwable error) { + StringWriter result = new StringWriter(); + PrintWriter printer = new PrintWriter(result); + error.printStackTrace(printer); + printer.close(); + return result.toString(); + } + + public static String format(String format, Object... objects) { + return String.format(format.replace("{}", "%s"), objects); + } + + public static boolean isLegalLanguage(String language) { + if (StringUtils.isBlank(language)) { + return false; + } + String[] code = language.split("-"); + boolean result; + if (code.length == 1 || code.length == 2) { + result = LANGUAGE_CODES.contains(code[0]); + if (code.length == 2) { + result &= LANGUAGE_SUBCODES.contains(code[1]); + } + } else { + result = false; + } + return result; + } + + public static boolean isLegalURI(String uri) { + try { + //noinspection ResultOfMethodCallIgnored + URI.create(uri); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean isLegalTime(String time) { + try { + Instant.parse(time); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean isLegalDuration(String duration) { + try { + Duration.parse(duration); + return true; + } catch (Exception e) { + return false; + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/ID.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/ID.java new file mode 100644 index 00000000..c9268ff7 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/ID.java @@ -0,0 +1,65 @@ +package com.xboe.module.scorm.common; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public final class ID { + + private final String identifier; + + private final String lmsContentPackageID; + + private final String lmsLearnerID; + + public ID(String identifier, String lmsContentPackageID, String lmsLearnerID) { + this.identifier = identifier; + this.lmsContentPackageID = lmsContentPackageID; + this.lmsLearnerID = lmsLearnerID; + } + + public final String getIdentifier() { + return identifier; + } + + public final String getLmsContentPackageID() { + return lmsContentPackageID; + } + + public final String getLmsLearnerID() { + return lmsLearnerID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ID id = (ID) o; + + return new EqualsBuilder() + .append(identifier, id.identifier) + .append(lmsContentPackageID, id.lmsContentPackageID) + .append(lmsLearnerID, id.lmsLearnerID) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(identifier) + .append(lmsContentPackageID) + .append(lmsLearnerID) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("identifier", identifier) + .append("lmsContentPackageID", lmsContentPackageID) + .append("lmsLearnerID", lmsLearnerID) + .toString(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/LMSPersistDriver.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/LMSPersistDriver.java new file mode 100644 index 00000000..4abacbdf --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/LMSPersistDriver.java @@ -0,0 +1,11 @@ +package com.xboe.module.scorm.common; + +public interface LMSPersistDriver { + + String querySCORMPackageZipFilePathBy(String lmsContentPackageID); + + int queryActivityAttemptCountBy(String lmsContentPackageID, String activityID, String learnerID); + + void saveActivityAttemptCount(String lmsContentPackageID, String activityID, String learnerID, int attemptCount); + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/LMSPersistDriverManager.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/LMSPersistDriverManager.java new file mode 100644 index 00000000..158470ac --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/common/LMSPersistDriverManager.java @@ -0,0 +1,31 @@ +package com.xboe.module.scorm.common; + +public class LMSPersistDriverManager { + + private static LMSPersistDriverManager instance; + + private LMSPersistDriver driver; + + private LMSPersistDriverManager() { + + } + + public static LMSPersistDriverManager getInstance() { + if (instance == null) { + synchronized (LMSPersistDriverManager.class) { + if (instance == null) { + instance = new LMSPersistDriverManager(); + } + } + } + return instance; + } + + public void registerDriver(LMSPersistDriver lmsPersistDriver) { + this.driver = lmsPersistDriver; + } + + public LMSPersistDriver getDriver() { + return driver; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/CommunicationSession.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/CommunicationSession.java new file mode 100644 index 00000000..af1f156a --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/CommunicationSession.java @@ -0,0 +1,44 @@ +package com.xboe.module.scorm.rte.api; + +import java.util.UUID; + +public class CommunicationSession { + + private String sessionID; + + private State state; + + CommunicationSession() { + sessionID = UUID.randomUUID().toString(); + state = State.NOT_INITIALIZED; + } + + String getSessionID() { + return sessionID; + } + + boolean isNotInitialized() { + return state.equals(State.NOT_INITIALIZED); + } + + boolean isRunning() { + return state.equals(State.RUNNING); + } + + boolean isTerminated() { + return state.equals(State.TERMINATED); + } + + void switchState(State state) { + if (state != null) { + this.state = state; + } + } + + public enum State { + NOT_INITIALIZED, + RUNNING, + TERMINATED + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/ElementParser.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/ElementParser.java new file mode 100644 index 00000000..815752e0 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/ElementParser.java @@ -0,0 +1,190 @@ +package com.xboe.module.scorm.rte.api; + +import java.lang.reflect.Field; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; + +import lombok.extern.slf4j.Slf4j; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.RuntimeData; +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.CollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.MapDataType; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.Diagnostic; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.CollectionScormResult; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +@Slf4j +public class ElementParser implements Delayed { + + private static DelayQueue<ElementParser> parserPool; + + static { + parserPool = new DelayQueue<>(); + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> parserPool.drainTo(new LinkedList<>()), + 1, 1, TimeUnit.SECONDS); + } + + private List<CollectionDataType> accessedCollectionElement; + private long lastAccessTime; + + private ElementParser() { + accessedCollectionElement = new LinkedList<>(); + lastAccessTime = System.currentTimeMillis(); + } + + static ScormResult parseSet(RuntimeData runtimeData, String elementName, String value) { + return getInstance().parse(runtimeData, true, elementName, value); + } + + static ScormResult parseGet(RuntimeData runtimeData, String elementName) { + return getInstance().parse(runtimeData, false, elementName, null); + } + + private static ElementParser getInstance() { + ElementParser parser = parserPool.poll(); + if (parser == null) { + parser = new ElementParser(); + } + return parser; + } + + private static void release(ElementParser parser) { + parser.lastAccessTime = System.currentTimeMillis(); + parserPool.offer(parser); + } + + private ScormResult parse(RuntimeData runtimeData, boolean isSet, String elementName, String value) { + if (StringUtils.isBlank(elementName)) { + return new ScormResult(isSet ? "false" : "", isSet ? ScormError.E_351 : ScormError.E_301, + Diagnostic.DATA_MODEL_ELEMENT_NOT_SPECIFIED); + } + String[] elementNames = elementName.split("\\."); + ScormResult scormResult = parse(runtimeData, elementNames, 0, isSet, value); + boolean success = scormResult.getError().equals(ScormError.E_0); + for (CollectionDataType collectionElement : accessedCollectionElement) { + collectionElement.syncNewInstance(success); + } + accessedCollectionElement.clear(); + release(this); + return scormResult; + } + + + private ScormResult parse(Object element, String[] elementNames, int index, boolean isSet, String value) { + if (index >= elementNames.length) { + return generateScormResult(elementNames, index, isSet); + } + if (isInteger(elementNames[index])) { + CollectionDataType collectionElement = (CollectionDataType) element; + CollectionScormResult collectionScormResult; + if (isSet) { + collectionScormResult = collectionElement.set(Integer.parseInt(elementNames[index])); + } else { + collectionScormResult = collectionElement.get(Integer.parseInt(elementNames[index])); + } + if (!collectionScormResult.getError().equals(ScormError.E_0)) { + return collectionScormResult; + } else { + accessedCollectionElement.add(collectionElement); + Object instance = collectionScormResult.getInstance(); + if (instance instanceof TerminalDataType){ + return parse((TerminalDataType) instance, isSet, value); + } else if (instance instanceof MapDataType) { + String key = index == elementNames.length - 1 ? null : elementNames[index + 1]; + return parse((MapDataType) instance, isSet, key, value); + } else { + return parse(instance, elementNames, index + 1, isSet, value); + } + } + } else { + for (Field field : element.getClass().getDeclaredFields()) { + if (field.isAnnotationPresent(Meta.class) && field.getDeclaredAnnotation(Meta.class).value().equals(elementNames[index])) { + try { + field.setAccessible(true); + Object childElement = field.get(element); + if (childElement instanceof TerminalDataType){ + return parse((TerminalDataType) childElement, isSet, value); + } else if (childElement instanceof MapDataType) { + String key = index == elementNames.length - 1 ? null : elementNames[index + 1]; + return parse((MapDataType) childElement, isSet, key, value); + } else { + return parse(childElement, elementNames, index + 1, isSet, value); + } + } catch (IllegalAccessException e) { + log.error(CommonUtils.stringifyError(e)); + } finally { + field.setAccessible(false); + } + break; + } + } + return generateScormResult(elementNames, index, isSet); + } + } + + private ScormResult parse(TerminalDataType terminalElement, boolean isSet, String value) { + ScormResult scormResult; + if (isSet) { + scormResult = terminalElement.set(value); + } else { + scormResult = terminalElement.get(); + } + return scormResult; + } + + private ScormResult parse(MapDataType mapElement, boolean isSet, String key, String value) { + ScormResult scormResult; + if (isSet) { + scormResult = mapElement.set(key, value); + } else { + scormResult = mapElement.get(key); + } + return scormResult; + } + + private ScormResult generateScormResult(String[] elementNames, int index, boolean isSet) { + if (index == elementNames.length - 1) { + switch (elementNames[index]) { + case "_children": + return new ScormResult(isSet ? "false" : "", isSet ? ScormError.E_351 : ScormError.E_301, + Diagnostic.DATA_MODEL_ELEMENT_DOSE_NOT_HAVE_CHILDREN); + case "_count": + return new ScormResult(isSet ? "false" : "", isSet ? ScormError.E_351 : ScormError.E_301, + Diagnostic.DATA_MODEL_ELEMENT_CANNOT_HAVE_COUNT); + case "_version": + return new ScormResult(isSet ? "false" : "", isSet ? ScormError.E_351 : ScormError.E_301, + Diagnostic.DATA_MODEL_ELEMENT_DOSE_NOT_HAVE_VERSION); + } + } + return new ScormResult(isSet ? "false" : "", ScormError.E_401); + } + + private boolean isInteger(String s) { + try { + Integer.parseInt(s); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public long getDelay(TimeUnit unit) { + return System.currentTimeMillis() - (lastAccessTime + 600000); + } + + @Override + public int compareTo(Delayed o) { + return (int)(this.lastAccessTime - ((ElementParser) o).lastAccessTime); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LMSLearnerInfo.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LMSLearnerInfo.java new file mode 100644 index 00000000..0adf90d0 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LMSLearnerInfo.java @@ -0,0 +1,12 @@ +package com.xboe.module.scorm.rte.api; + +public interface LMSLearnerInfo { + + String getLearnerID(); + + void setLearnerID(String learnerID); + + String getLearnerName(); + + void setLearnerName(String learnerName); +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LearnerAttempt.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LearnerAttempt.java new file mode 100644 index 00000000..728234f4 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LearnerAttempt.java @@ -0,0 +1,485 @@ +package com.xboe.module.scorm.rte.api; + +import java.math.BigDecimal; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; + +import lombok.extern.slf4j.Slf4j; + +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.Item; +import com.xboe.module.scorm.cam.model.Objective; +import com.xboe.module.scorm.cam.model.Objectives; +import com.xboe.module.scorm.cam.model.Sequencing; +import com.xboe.module.scorm.cam.model.util.CPUtils; +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.rte.model.Data; +import com.xboe.module.scorm.rte.model.Objectives.Instance; +import com.xboe.module.scorm.rte.model.RuntimeData; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +@Slf4j +public class LearnerAttempt { + + private ID attemptID; + + private State state; + + private RuntimeData runtimeData; + + private Map<String, LearnerSession> learnerSessionMap; + + private LearnerSession currentLearnerSession; + + private Reason lastLearnerClosedReason; + + private int lastErrorCode; + + private String lastErrorString; + + private String lastDiagnostic; + + LearnerAttempt(ID attemptID) { + this.attemptID = attemptID; + state = State.INITIALIZED; + learnerSessionMap = new ConcurrentHashMap<>(); + this.lastLearnerClosedReason = Reason.NONE; + lastErrorCode = ScormError.E_0.getCode(); + lastErrorString = ScormError.E_0.getMsg(); + lastDiagnostic = ScormError.E_0.getMsg(); + } + + ID getAttemptID() { + return attemptID; + } + + boolean isJustCreate() { + return state.equals(State.JUST_CREATE); + } + + boolean isInitialized() { + return state.equals(State.INITIALIZED); + } + + boolean isOpen() { + return state.equals(State.OPEN); + } + + boolean isSuspended() { + return state.equals(State.SUSPENDED); + } + + boolean isClosed() { + return state.equals(State.CLOSED); + } + + public RuntimeData getRuntimeData() { + return runtimeData; + } + + String initialize(String parameter) { + if (currentLearnerSession == null || currentLearnerSession.isNotOpen() + || currentLearnerSession.isClosed() || !currentLearnerSession.isOpen()) { + setError(ScormError.E_102); + return "false"; + } + if (StringUtils.isNotBlank(parameter)) { + setError(ScormError.E_201); + return "false"; + } + ScormResult scormResult = currentLearnerSession.initialize(); + setError(scormResult.getError(), scormResult.getDiagnostic()); + return scormResult.getReturnValue(); + } + + public String terminate(String parameter) { + if (currentLearnerSession == null || currentLearnerSession.isNotOpen() + || currentLearnerSession.isClosed() || !currentLearnerSession.isOpen()) { + setError(ScormError.E_111); + return "false"; + } + if (StringUtils.isNotBlank(parameter)) { + setError(ScormError.E_201); + return "false"; + } + ScormResult scormResult = currentLearnerSession.terminate(); + setError(scormResult.getError(), scormResult.getDiagnostic()); + return scormResult.getReturnValue(); + } + + String getValue(String elementName) { + if (currentLearnerSession == null || currentLearnerSession.isNotOpen() + || currentLearnerSession.isClosed() || !currentLearnerSession.isOpen()) { + setError(ScormError.E_301); + return ""; + } + if (currentLearnerSession.isCommunicationNotInitialized()) { + setError(ScormError.E_122); + return ""; + } + if (currentLearnerSession.isCommunicationTerminated()) { + setError(ScormError.E_123); + return ""; + } + ScormResult scormResult = ElementParser.parseGet(runtimeData, elementName); + setError(scormResult.getError(), scormResult.getDiagnostic()); + return scormResult.getReturnValue(); + } + + String setValue(String elementName, String value) { + if (currentLearnerSession == null || currentLearnerSession.isNotOpen() + || currentLearnerSession.isClosed() || !currentLearnerSession.isOpen()) { + setError(ScormError.E_351); + return ""; + } + if (currentLearnerSession.isCommunicationNotInitialized()) { + setError(ScormError.E_132); + return ""; + } + if (currentLearnerSession.isCommunicationTerminated()) { + setError(ScormError.E_133); + return ""; + } + ScormResult scormResult = ElementParser.parseSet(runtimeData, elementName, value); + setError(scormResult.getError(), scormResult.getDiagnostic()); + return scormResult.getReturnValue(); + } + + String commit(String parameter) { + if (currentLearnerSession == null || currentLearnerSession.isNotOpen() + || currentLearnerSession.isClosed() || !currentLearnerSession.isOpen()) { + setError(ScormError.E_391); + return "false"; + } + if (currentLearnerSession.isCommunicationNotInitialized()) { + setError(ScormError.E_142); + return "false"; + } + if (currentLearnerSession.isCommunicationTerminated()) { + setError(ScormError.E_143); + return "false"; + } + if (StringUtils.isNotBlank(parameter)) { + setError(ScormError.E_201); + return "false"; + } + setError(ScormError.E_0); + return "true"; + } + + String getLastErrorCode() { + return String.valueOf(lastErrorCode); + } + + String getLastErrorString() { + return lastErrorString; + } + + String getLastDiagnostic() { + return lastDiagnostic; + } + + boolean openLearnerSession() { + if (isJustCreate()) { + log.error("creat learner session before initialize run-time data"); + return false; + } + if (isClosed()) { + log.error("learner attempt already closed"); + return false; + } + if (currentLearnerSession != null) { + log.error("learner session already exist"); + return false; + } + currentLearnerSession = new LearnerSession(); + learnerSessionMap.put(currentLearnerSession.getSessionID(), currentLearnerSession); + currentLearnerSession.open(); + clearExit(); + clearSessionTime(); + updateEntry(); + state = State.OPEN; + return true; + } + + public boolean closeLearnerSession(boolean isSuspendAllNavigationRequest) { + if (isJustCreate()) { + log.error("close learner session before initialize run-time data"); + return false; + } + if (isClosed()) { + log.error("learner attempt already closed"); + return false; + } + if (currentLearnerSession == null) { + log.error("learner session not exist"); + return false; + } + currentLearnerSession.close(); + currentLearnerSession = null; + accumulateTotalTime(); + updateExitReason(isSuspendAllNavigationRequest); + if (lastLearnerClosedReason == Reason.EXIT_BY_SUSPEND || lastLearnerClosedReason == Reason.SUSPEND_ALL_NAVIGATION_REQUEST) { + state = State.SUSPENDED; + } else { + state = State.CLOSED; + } + return true; + } + + // invoke when a new learner session created + private void clearExit() { + runtimeData.getCmi().getExit().getExit().setValue(""); + } + + // invoke when a new learner session created + private void clearSessionTime() { + runtimeData.getCmi().getSessionTime().getSessionTime().setValue(null); + } + + // invoke when a new learner session created + private void updateEntry() { + if (learnerSessionMap.size() == 1) { + runtimeData.getCmi().getEntry().getEntry().setValue("ab-initio"); + } else if (lastLearnerClosedReason == Reason.EXIT_BY_SUSPEND) { + runtimeData.getCmi().getEntry().getEntry().setValue("resume"); + } else if (lastLearnerClosedReason == Reason.EXIT_BY_LOGOUT) { + // a new learner attempt on the SCO is being beginning after a learner session + runtimeData.getCmi().getEntry().getEntry().setValue("ab-initio"); + } else if (lastLearnerClosedReason == Reason.SUSPEND_ALL_NAVIGATION_REQUEST) { + runtimeData.getCmi().getEntry().getEntry().setValue("resume"); + } else { + runtimeData.getCmi().getEntry().getEntry().setValue(""); + } + } + + private void updateExitReason(boolean isSuspendAllNavigationRequest) { + if (isSuspendAllNavigationRequest) { + lastLearnerClosedReason = Reason.SUSPEND_ALL_NAVIGATION_REQUEST; + } else { + String exit = runtimeData.getCmi().getExit().getExit().getValue(); + if ("suspend".equals(exit)) { + lastLearnerClosedReason = Reason.EXIT_BY_SUSPEND; + } else if ("logout".equals(exit)) { + lastLearnerClosedReason = Reason.EXIT_BY_LOGOUT; + } else { + lastLearnerClosedReason = Reason.EXIT_BY_OTHERS; + } + } + } + + // invoke when a learner session closed + private void accumulateTotalTime() { + String currentTotalTime = runtimeData.getCmi().getTotalTime().getTotalTime().getValue(); + String currentSessionTime = runtimeData.getCmi().getSessionTime().getSessionTime().getValue(); + if (currentSessionTime != null) { + runtimeData.getCmi().getTotalTime().getTotalTime().setValue( + Duration.parse(currentTotalTime).plus(Duration.parse(currentSessionTime)).toString()); + } + } + + boolean initRuntimeData(ContentPackage contentPackage, Item item, LMSLearnerInfo lmsLearnerInfo) { + if (runtimeData != null) { + return false; + } + if (contentPackage == null || item == null) { + return false; + } + + runtimeData = new RuntimeData(); + + initCompletionThreshold(item); + + initLaunchData(item); + + initLearnerID(lmsLearnerInfo); + + initLearnerName(lmsLearnerInfo); + + initLearnerPreference(); + + initMaximumTimeAllowed(item); + + initObjectives(contentPackage, item); + + initScaledPassingScore(contentPackage, item); + + initTimeLimitAction(item); + + initADLData(item); + + state = State.INITIALIZED; + + return true; + } + + private void initCompletionThreshold(Item item) { + if (item.getCompletionThreshold() != null && item.getCompletionThreshold().isCompletedByMeasure()) { + runtimeData.getCmi().getCompletionThreshold().getCompletionThreshold().setValue( + new BigDecimal(item.getCompletionThreshold().getMinProgressMeasure().getDecimalValue().doubleValue()) + .setScale(7, BigDecimal.ROUND_HALF_UP)); + } else if (item.parentIsItem()){ + initCompletionThreshold(item.getParentItem()); + } else if (item.parentIsOrganization() && item.getParentOrganization().getCompletionThreshold() != null + && item.getParentOrganization().getCompletionThreshold().isCompletedByMeasure()) { + runtimeData.getCmi().getCompletionThreshold().getCompletionThreshold().setValue( + new BigDecimal(item.getParentOrganization().getCompletionThreshold().getMinProgressMeasure().getDecimalValue().doubleValue()) + .setScale(7, BigDecimal.ROUND_HALF_UP)); + } + } + + private void initLaunchData(Item item) { + if (StringUtils.isNotBlank(item.getDataFromLMS())) { + runtimeData.getCmi().getLaunchData().getLaunchData().setValue(item.getDataFromLMS()); + } + } + + private void initLearnerID(LMSLearnerInfo lmsLearnerInfo) { + runtimeData.getCmi().getLearnerId().getLearnerId().setValue(lmsLearnerInfo.getLearnerID()); + } + + private void initLearnerName(LMSLearnerInfo lmsLearnerInfo) { + runtimeData.getCmi().getLearnerName().getLearnerName().setValue(lmsLearnerInfo.getLearnerName()); + } + + private void initLearnerPreference() { + runtimeData.getCmi().getLearnerPreference().getAudioLevel().getAudioLevel().setValue( + new BigDecimal(1).setScale(7, BigDecimal.ROUND_HALF_UP)); + runtimeData.getCmi().getLearnerPreference().getLanguage().setLanguage(""); + runtimeData.getCmi().getLearnerPreference().getDeliverySpeed().setValue( + new BigDecimal(1).setScale(7, BigDecimal.ROUND_HALF_UP)); + runtimeData.getCmi().getLearnerPreference().getAudioCaptioning().setValue("0"); + } + + private void initMaximumTimeAllowed(Item item) { + if (item.getSequencing() != null && item.getSequencing().getLimitConditions() != null + && StringUtils.isNotBlank(item.getSequencing().getLimitConditions().getAttemptAbsoluteDurationLimit())) { + String attemptAbsoluteDurationLimit = item.getSequencing().getLimitConditions().getAttemptAbsoluteDurationLimit(); + runtimeData.getCmi().getMaximumTimeAllowed().getMaximumTimeAllowed().setValue(attemptAbsoluteDurationLimit); + } + } + + // TODO: SN RTE-4-96 + private void initObjectives(ContentPackage contentPackage, Item item) { + if (item.getSequencing() == null) { + return; + } + initObjectives(item.getSequencing()); + // TODO: 是否有必要 +// if (item.getSequencing().getIdRef() != null && StringUtils.isNotBlank(item.getSequencing().getIdRef().getValue())) { +// Sequencing sequencing = CPUtils.findSequencingByID(contentPackage.getManifest().getSequencingCollection(), +// item.getSequencing().getIdRef().getValue()); +// if (sequencing != null) { +// initObjectives(sequencing); +// } +// } + runtimeData.getCmi().getObjectives().getCount().setValue( + runtimeData.getCmi().getObjectives().getInstances().size()); + } + + private void initObjectives(Sequencing sequencing) { + if (sequencing.getObjectives() == null) { + return; + } + Objectives objectives = sequencing.getObjectives(); + Objective primaryObjective = objectives.getPrimaryObjective(); + if (primaryObjective.getObjectiveID() != null) { + Instance primaryInstance = new Instance(runtimeData.getCmi().getObjectives()); + primaryInstance.getId().setValue(primaryObjective.getObjectiveID().getValue()); + runtimeData.getCmi().getObjectives().getInstances().add(primaryInstance); + } + for (Objective objective : objectives.getObjectiveList()) { + Instance instance = new Instance(runtimeData.getCmi().getObjectives()); + instance.getId().setValue(objective.getObjectiveID().getValue()); + runtimeData.getCmi().getObjectives().getInstances().add(instance); + } + } + + private void initScaledPassingScore(ContentPackage contentPackage, Item item) { + if (item.getSequencing() == null) { + return; + } + Objectives objectives = null; + if (item.getSequencing().getObjectives() != null) { + objectives = item.getSequencing().getObjectives(); + } else { + if (item.getSequencing().getIdRef() != null && StringUtils.isNotBlank(item.getSequencing().getIdRef().getValue())) { + Sequencing sequencing = CPUtils.findSequencingByID(contentPackage.getManifest().getSequencingCollection(), + item.getSequencing().getIdRef().getValue()); + if (sequencing != null) { + objectives = sequencing.getObjectives(); + } + } + } + if (objectives == null) { + return; + } + Objective primaryObjective = objectives.getPrimaryObjective(); + if (primaryObjective == null) { + return; + } + if (primaryObjective.isSatisfiedByMeasure()) { + double value; + if (primaryObjective.getMinNormalizedMeasure() != null + && primaryObjective.getMinNormalizedMeasure().getDecimalValue() != null) { + value = primaryObjective.getMinNormalizedMeasure().getDecimalValue().doubleValue(); + } else { + value = 1.0; + } + runtimeData.getCmi().getScaledPassingScore().getScaledPassingScore().setValue( + new BigDecimal(value).setScale(7, BigDecimal.ROUND_HALF_UP)); + } + } + + private void initTimeLimitAction(Item item) { + if (StringUtils.isNotBlank(item.getTimeLimitAction())) { + runtimeData.getCmi().getTimeLimitAction().getTimeLimitAction().setValue(item.getTimeLimitAction()); + } + } + + private void initADLData(Item item) { + if (item.getData() != null) { + for (com.xboe.module.scorm.cam.model.Map map : item.getData().getMapList()) { + Data.Instance instance = new Data.Instance(); + instance.getId().setValue(map.getTargetID().getValue()); + runtimeData.getAdl().getData().getInstances().add(instance); + } + runtimeData.getAdl().getData().getCount().setValue(runtimeData.getAdl().getData().getInstances().size()); + } + } + + private void setError(ScormError scormError) { + setError(scormError, scormError.getMsg()); + } + + private void setError(ScormError scormError, String diagnostic) { + lastErrorCode = scormError.getCode(); + lastErrorString = scormError.getMsg(); + lastDiagnostic = diagnostic; + } + + enum State { + JUST_CREATE, + INITIALIZED, + OPEN, + SUSPENDED, + CLOSED + } + + enum Reason { + NONE, + EXIT_BY_SUSPEND, + EXIT_BY_LOGOUT, + EXIT_BY_OTHERS, + SUSPEND_ALL_NAVIGATION_REQUEST + } + + static void main(String[] args) { + System.out.println(Duration.ZERO.toString()); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LearnerSession.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LearnerSession.java new file mode 100644 index 00000000..b44cceff --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/LearnerSession.java @@ -0,0 +1,111 @@ +package com.xboe.module.scorm.rte.api; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class LearnerSession { + + private String sessionID; + + private State state; + + private Map<String, CommunicationSession> communicationSessionMap; + + private CommunicationSession currentCommunicationSession; + + LearnerSession() { + sessionID = UUID.randomUUID().toString(); + state = State.NOT_OPEN; + communicationSessionMap = new ConcurrentHashMap<>(); + } + + String getSessionID() { + return sessionID; + } + + boolean isNotOpen() { + return state.equals(State.NOT_OPEN); + } + + boolean isOpen() { + return state.equals(State.OPEN); + } + + boolean isClosed() { + return state.equals(State.CLOSED); + } + + boolean isCommunicationNotInitialized() { + return state == State.OPEN && currentCommunicationSession != null && currentCommunicationSession.isNotInitialized(); + } + + boolean isCommunicationRunning() { + return state == State.OPEN && currentCommunicationSession != null && currentCommunicationSession.isRunning(); + } + + boolean isCommunicationTerminated() { + return state == State.OPEN && currentCommunicationSession != null && currentCommunicationSession.isTerminated(); + } + + ScormResult initialize() { + if (currentCommunicationSession == null && state == State.OPEN) { + currentCommunicationSession = new CommunicationSession(); + communicationSessionMap.put(currentCommunicationSession.getSessionID(), currentCommunicationSession); + } else { + return new ScormResult("false", ScormError.E_102); + } + if (currentCommunicationSession.isNotInitialized()) { + currentCommunicationSession.switchState(CommunicationSession.State.RUNNING); + return new ScormResult("true", ScormError.E_0); + } + if (currentCommunicationSession.isRunning()) { + return new ScormResult("false", ScormError.E_103); + } + if (currentCommunicationSession.isTerminated()) { + return new ScormResult("false", ScormError.E_104); + } + return new ScormResult("false", ScormError.E_102); + } + + ScormResult terminate() { + if (currentCommunicationSession == null) { + return new ScormResult("false", ScormError.E_111); + } + if (currentCommunicationSession.isNotInitialized()) { + return new ScormResult("false", ScormError.E_112); + } + if (currentCommunicationSession.isRunning()) { + currentCommunicationSession.switchState(CommunicationSession.State.TERMINATED); + currentCommunicationSession = null; + return new ScormResult("true", ScormError.E_0); + } + if (currentCommunicationSession.isTerminated()) { + return new ScormResult("false", ScormError.E_113); + } + return new ScormResult("false", ScormError.E_111); + } + + void open() { + state = State.OPEN; + } + + void close() { + if (currentCommunicationSession != null) { + currentCommunicationSession.switchState(CommunicationSession.State.TERMINATED); + communicationSessionMap.put(currentCommunicationSession.getSessionID(), currentCommunicationSession); + currentCommunicationSession = null; + } + state = State.CLOSED; + } + + enum State { + NOT_OPEN, + OPEN, + CLOSED + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/SCORMRuntimeManager.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/SCORMRuntimeManager.java new file mode 100644 index 00000000..3f1c2c2d --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/api/SCORMRuntimeManager.java @@ -0,0 +1,211 @@ +package com.xboe.module.scorm.rte.api; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import lombok.extern.slf4j.Slf4j; + +import com.xboe.module.scorm.cam.load.SCORMPackageManager; +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.Item; +import com.xboe.module.scorm.cam.model.util.CPUtils; +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.rte.model.error.ScormError; + +@Slf4j +public class SCORMRuntimeManager { + + private static SCORMRuntimeManager instance; + + private SCORMPackageManager scormPackageManager; + + private Map<ID, LearnerAttempt> learnerAttemptMap; + + private SCORMRuntimeManager() { + scormPackageManager = SCORMPackageManager.getInstance(); + learnerAttemptMap = new ConcurrentHashMap<>(); + } + + public static SCORMRuntimeManager getInstance() { + if (instance == null) { + synchronized (SCORMRuntimeManager.class) { + if (instance == null) { + instance = new SCORMRuntimeManager(); + } + } + } + return instance; + } + + public boolean launch(String lmsContentPackageID, String activityItemID, LMSLearnerInfo lmsLearnerInfo) { + return launch(lmsContentPackageID, activityItemID, lmsLearnerInfo, false); + } + + public boolean launch(String lmsContentPackageID, String activityItemID, LMSLearnerInfo lmsLearnerInfo, boolean reloadIfPresent) { + ContentPackage contentPackage = scormPackageManager.launch(lmsContentPackageID, reloadIfPresent); + if (contentPackage == null) { + return false; + } + return launch(lmsContentPackageID, contentPackage, lmsLearnerInfo, activityItemID, reloadIfPresent); + } + + private boolean launch(String lmsContentPackageID, ContentPackage contentPackage, LMSLearnerInfo lmsLearnerInfo, + String activityItemID, boolean reloadIfPresent) { + Item item = CPUtils.findItemByIdentifier(contentPackage, activityItemID); + if (item == null) { + log.error("not found the activity's item {}", activityItemID); + return false; + } + ID id = new ID(activityItemID, lmsContentPackageID, lmsLearnerInfo.getLearnerID()); + if (learnerAttemptMap.containsKey(id) && !reloadIfPresent) { + return true; + } + LearnerAttempt learnerAttempt = new LearnerAttempt(id); + if (!learnerAttempt.initRuntimeData(contentPackage, item, lmsLearnerInfo)) { + log.error("init run-time data error"); + return false; + } + learnerAttemptMap.put(learnerAttempt.getAttemptID(), learnerAttempt); + return true; + } + + public void unlaunch(LMSLearnerInfo lmsLearnerInfo) { + List<ID> shouldDelete = new LinkedList<>(); + for (ID attemptID : learnerAttemptMap.keySet()) { + if (attemptID.getLmsLearnerID().equals(lmsLearnerInfo.getLearnerID())) { + shouldDelete.add(attemptID); + } + } + unlaunch(shouldDelete); + } + + public void unlaunch(String lmsContentPackageID) { + List<ID> shouldDelete = new LinkedList<>(); + for (ID attemptID : learnerAttemptMap.keySet()) { + if (attemptID.getLmsContentPackageID().equals(lmsContentPackageID)) { + shouldDelete.add(attemptID); + } + } + unlaunch(shouldDelete); + } + + public void unlaunch(LMSLearnerInfo lmsLearnerInfo, String lmsContentPackageID) { + List<ID> shouldDelete = new LinkedList<>(); + for (ID attemptID : learnerAttemptMap.keySet()) { + if (attemptID.getLmsLearnerID().equals(lmsLearnerInfo.getLearnerID()) + && attemptID.getLmsContentPackageID().equals(lmsContentPackageID)) { + shouldDelete.add(attemptID); + } + } + unlaunch(shouldDelete); + } + + public void unlaunch(ID attemptID) { + learnerAttemptMap.remove(attemptID); + } + + private void unlaunch(List<ID> shouldDelete) { + for (ID attemptID : shouldDelete) { + unlaunch(attemptID); + } + } + + public LearnerAttempt getLearnerAttempt(ID id) { + return learnerAttemptMap.get(id); + } + + public String initialize(ID attemptID, String parameter) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + learnerAttempt.openLearnerSession(); + return learnerAttempt.initialize(parameter); + } else { + return "false"; + } + } + + public String terminate(ID attemptID, String parameter) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + String returnValue = learnerAttempt.terminate(parameter); + learnerAttempt.closeLearnerSession(false); + return returnValue; + } else { + return "false"; + } + } + + public String getValue(ID attemptID, String elementName) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + return learnerAttempt.getValue(elementName); + } else { + return ""; + } + } + + public String setValue(ID attemptID, String elementName, String value) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + return learnerAttempt.setValue(elementName, value); + } else { + return "false"; + } + } + + public String commit(ID attemptID, String parameter) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + return learnerAttempt.commit(parameter); + } else { + return "false"; + } + } + + public String getLastErrorCode(ID attemptID) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + return learnerAttempt.getLastErrorCode(); + } else { + return String.valueOf(ScormError.E_101.getCode()); + } + } + + public String getLastErrorString(ID attemptID) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + return learnerAttempt.getLastErrorString(); + } else { + return ScormError.E_101.getMsg(); + } + } + + public String getLastDiagnostic(ID attemptID) { + LearnerAttempt learnerAttempt = learnerAttemptMap.get(attemptID); + if (learnerAttempt != null) { + return learnerAttempt.getLastDiagnostic(); + } else { + return "please try again"; + } + } + + public String getErrorString(int errorCode) { + for (ScormError value : ScormError.values()) { + if (value.getCode() == errorCode) { + return value.getMsg(); + } + } + return "Undefined error code - " + errorCode; + } + + public String getDiagnostic(int errorCode) { + for (ScormError value : ScormError.values()) { + if (value.getCode() == errorCode) { + return value.getMsg(); + } + } + return "Undefined error code - " + errorCode; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ADL.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ADL.java new file mode 100644 index 00000000..3b93157b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ADL.java @@ -0,0 +1,35 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; + +public class ADL implements GeneralDataType { + + @Meta("data") + private Data data; + + @Meta("nav") + private Nav nav; + + public ADL() { + this.data = new Data(); + this.nav = new Nav(); + } + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public Nav getNav() { + return nav; + } + + public ADL setNav(Nav nav) { + this.nav = nav; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/AudioLevel.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/AudioLevel.java new file mode 100644 index 00000000..4e8c0c71 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/AudioLevel.java @@ -0,0 +1,35 @@ +package com.xboe.module.scorm.rte.model; + +import java.math.BigDecimal; + +import com.xboe.module.scorm.rte.model.datatype.Real7WithMin; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class AudioLevel implements TerminalDataType { + + private Real7WithMin audioLevel; + + public AudioLevel() { + this.audioLevel = new Real7WithMin(0); + this.audioLevel.setValue(new BigDecimal(0).setScale(7, BigDecimal.ROUND_HALF_UP)); + } + + @Override + public ScormResult set(String value) { + return this.audioLevel.set(value); + } + + @Override + public ScormResult get() { + return this.audioLevel.get(); + } + + public Real7WithMin getAudioLevel() { + return audioLevel; + } + + public void setAudioLevel(Real7WithMin audioLevel) { + this.audioLevel = audioLevel; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CMI.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CMI.java new file mode 100644 index 00000000..7529c020 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CMI.java @@ -0,0 +1,317 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; + +public class CMI implements GeneralDataType { + + @Meta(value = "_version", writable = false) + private CharacterString version; + + @Meta("comments_from_learner") + private CommentsFromLearner commentsFromLearner; + + @Meta("comments_from_lms") + private CommentsFromLMS commentsFromLMS; + + @Meta("completion_status") + private CompletionStatus completionStatus; + + @Meta(value = "completion_threshold", writable = false) + private CompletionThreshold completionThreshold; + + @Meta(value = "credit", writable = false) + private Credit credit; + + @Meta(value = "entry", writable = false) + private Entry entry; + + @Meta(value = "exit", readable = false) + private Exit exit; + + @Meta("interactions") + private Interactions interactions; + + @Meta(value = "launch_data", writable = false) + private LaunchData launchData; + + @Meta(value = "learner_id", writable = false) + private LearnerId learnerId; + + @Meta(value = "learner_name", writable = false) + private LearnerName learnerName; + + @Meta("learner_preference") + private LearnerPreference learnerPreference; + + @Meta("location") + private Location location; + + @Meta(value = "maximum_time_allowed", writable = false) + private MaximumTimeAllowed maximumTimeAllowed; + + @Meta(value = "mode", writable = false) + private Mode mode; + + @Meta("objectives") + private Objectives objectives; + + @Meta("progress_measure") + private ProgressMeasure progressMeasure; + + @Meta(value = "scaled_passing_score", writable = false) + private ScaledPassingScore scaledPassingScore; + + @Meta("score") + private Score score; + + @Meta(value = "session_time", readable = false) + private SessionTime sessionTime; + + @Meta("success_status") + private SuccessStatus successStatus; + + @Meta("suspend_data") + private SuspendData suspendData; + + @Meta(value = "time_limit_action", writable = false) + private TimeLimitAction timeLimitAction; + + @Meta(value = "total_time", writable = false) + private TotalTime totalTime; + + public CMI() { + this.version = new CharacterString("1.0"); + this.commentsFromLearner = new CommentsFromLearner(); + this.commentsFromLMS = new CommentsFromLMS(); + this.completionStatus = new CompletionStatus(this); + this.completionThreshold = new CompletionThreshold(); + this.credit = new Credit(); + this.entry = new Entry(); + this.exit = new Exit(); + this.interactions = new Interactions(); + this.launchData = new LaunchData(); + this.learnerId = new LearnerId(); + this.learnerName = new LearnerName(); + this.learnerPreference = new LearnerPreference(); + this.location = new Location(); + this.maximumTimeAllowed = new MaximumTimeAllowed(); + this.mode = new Mode(); + this.objectives = new Objectives(); + this.progressMeasure = new ProgressMeasure(); + this.scaledPassingScore = new ScaledPassingScore(); + this.score = new Score(); + this.sessionTime = new SessionTime(); + this.successStatus = new SuccessStatus(this); + this.suspendData = new SuspendData(); + this.timeLimitAction = new TimeLimitAction(); + this.totalTime = new TotalTime(); + registerHandler(); + } + + private void registerHandler() { + version.registerSetHandler(new ReadOnlyHandler()); + } + + public CharacterString getVersion() { + return version; + } + + public void setVersion(CharacterString version) { + this.version = version; + } + + public CommentsFromLearner getCommentsFromLearner() { + return commentsFromLearner; + } + + public void setCommentsFromLearner(CommentsFromLearner commentsFromLearner) { + this.commentsFromLearner = commentsFromLearner; + } + + public CommentsFromLMS getCommentsFromLMS() { + return commentsFromLMS; + } + + public void setCommentsFromLMS(CommentsFromLMS commentsFromLMS) { + this.commentsFromLMS = commentsFromLMS; + } + + public CompletionStatus getCompletionStatus() { + return completionStatus; + } + + public void setCompletionStatus(CompletionStatus completionStatus) { + this.completionStatus = completionStatus; + } + + public CompletionThreshold getCompletionThreshold() { + return completionThreshold; + } + + public void setCompletionThreshold(CompletionThreshold completionThreshold) { + this.completionThreshold = completionThreshold; + } + + public Credit getCredit() { + return credit; + } + + public void setCredit(Credit credit) { + this.credit = credit; + } + + public Entry getEntry() { + return entry; + } + + public void setEntry(Entry entry) { + this.entry = entry; + } + + public Exit getExit() { + return exit; + } + + public void setExit(Exit exit) { + this.exit = exit; + } + + public Interactions getInteractions() { + return interactions; + } + + public void setInteractions(Interactions interactions) { + this.interactions = interactions; + } + + public LaunchData getLaunchData() { + return launchData; + } + + public void setLaunchData(LaunchData launchData) { + this.launchData = launchData; + } + + public LearnerId getLearnerId() { + return learnerId; + } + + public void setLearnerId(LearnerId learnerId) { + this.learnerId = learnerId; + } + + public LearnerName getLearnerName() { + return learnerName; + } + + public void setLearnerName(LearnerName learnerName) { + this.learnerName = learnerName; + } + + public LearnerPreference getLearnerPreference() { + return learnerPreference; + } + + public void setLearnerPreference(LearnerPreference learnerPreference) { + this.learnerPreference = learnerPreference; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public MaximumTimeAllowed getMaximumTimeAllowed() { + return maximumTimeAllowed; + } + + public void setMaximumTimeAllowed(MaximumTimeAllowed maximumTimeAllowed) { + this.maximumTimeAllowed = maximumTimeAllowed; + } + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + public Objectives getObjectives() { + return objectives; + } + + public void setObjectives(Objectives objectives) { + this.objectives = objectives; + } + + public ProgressMeasure getProgressMeasure() { + return progressMeasure; + } + + public void setProgressMeasure(ProgressMeasure progressMeasure) { + this.progressMeasure = progressMeasure; + } + + public ScaledPassingScore getScaledPassingScore() { + return scaledPassingScore; + } + + public void setScaledPassingScore(ScaledPassingScore scaledPassingScore) { + this.scaledPassingScore = scaledPassingScore; + } + + public Score getScore() { + return score; + } + + public void setScore(Score score) { + this.score = score; + } + + public SessionTime getSessionTime() { + return sessionTime; + } + + public void setSessionTime(SessionTime sessionTime) { + this.sessionTime = sessionTime; + } + + public SuccessStatus getSuccessStatus() { + return successStatus; + } + + public void setSuccessStatus(SuccessStatus successStatus) { + this.successStatus = successStatus; + } + + public SuspendData getSuspendData() { + return suspendData; + } + + public void setSuspendData(SuspendData suspendData) { + this.suspendData = suspendData; + } + + public TimeLimitAction getTimeLimitAction() { + return timeLimitAction; + } + + public void setTimeLimitAction(TimeLimitAction timeLimitAction) { + this.timeLimitAction = timeLimitAction; + } + + public TotalTime getTotalTime() { + return totalTime; + } + + public void setTotalTime(TotalTime totalTime) { + this.totalTime = totalTime; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CommentsFromLMS.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CommentsFromLMS.java new file mode 100644 index 00000000..bb79d20e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CommentsFromLMS.java @@ -0,0 +1,128 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.AbstractCollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Int; +import com.xboe.module.scorm.rte.model.datatype.LocalizedString; +import com.xboe.module.scorm.rte.model.datatype.Time; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class CommentsFromLMS extends AbstractCollectionDataType<CommentsFromLMS.Instance> { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta(value = "_count", writable = false) + private Int count; + + public CommentsFromLMS() { + children = new CharacterString("comment,location,timestamp"); + count = new Int(0); + registerHandler(); + } + + @Override + protected Instance newInstance() { + return new Instance(); + } + + @Override + protected void addCount() { + count.setValue(count.getValue() + 1); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + count.registerSetHandler(new ReadOnlyHandler()); + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public Int getCount() { + return count; + } + + public void setCount(Int count) { + this.count = count; + } + + public static class Instance implements GeneralDataType { + + @Meta(value = "comment", writable = false) + private LocalizedString comment; + + @Meta(value = "location", writable = false) + private CharacterString location; + + @Meta(value = "timestamp", writable = false) + private Time timestamp; + + public Instance() { + this.comment = new LocalizedString(); + this.location = new CharacterString(); + this.timestamp = new Time(); + registerHandler(); + } + + private void registerHandler() { + comment.registerGetHandler(context -> { + LocalizedString c = (LocalizedString) context; + if (c.getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler(new ReadOnlyHandler()); + + location.registerGetHandler(context -> { + CharacterString l = (CharacterString) context; + if (l.getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler(new ReadOnlyHandler()); + + timestamp.registerGetHandler(context -> { + Time t = (Time) context; + if (t.getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler(new ReadOnlyHandler()); + } + + public LocalizedString getComment() { + return comment; + } + + public void setComment(LocalizedString comment) { + this.comment = comment; + } + + public CharacterString getLocation() { + return location; + } + + public void setLocation(CharacterString location) { + this.location = location; + } + + public Time getTimestamp() { + return timestamp; + } + + public void setTimestamp(Time timestamp) { + this.timestamp = timestamp; + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CommentsFromLearner.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CommentsFromLearner.java new file mode 100644 index 00000000..e8afaa46 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CommentsFromLearner.java @@ -0,0 +1,129 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.AbstractCollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Int; +import com.xboe.module.scorm.rte.model.datatype.LocalizedString; +import com.xboe.module.scorm.rte.model.datatype.Time; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class CommentsFromLearner extends AbstractCollectionDataType<CommentsFromLearner.Instance> { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta(value = "_count", writable = false) + private Int count; + + public CommentsFromLearner() { + children = new CharacterString("comment,location,timestamp"); + count = new Int(0); + registerHandler(); + } + + @Override + protected Instance newInstance() { + return new Instance(); + } + + @Override + protected void addCount() { + count.setValue(count.getValue() + 1); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + count.registerSetHandler(new ReadOnlyHandler()); + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public Int getCount() { + return count; + } + + public void setCount(Int count) { + this.count = count; + } + + public static class Instance implements GeneralDataType { + + @Meta("comment") + private LocalizedString comment; + + @Meta("location") + private CharacterString location; + + @Meta("timestamp") + private Time timestamp; + + public Instance() { + this.comment = new LocalizedString(); + this.location = new CharacterString(); + this.timestamp = new Time(); + registerHandler(); + } + + private void registerHandler() { + comment.registerGetHandler(context -> { + LocalizedString c = (LocalizedString) context; + if (c.getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + + location.registerGetHandler(context -> { + CharacterString l = (CharacterString) context; + if (l.getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + + timestamp.registerGetHandler(context -> { + Time t = (Time) context; + if (t.getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + + } + + public LocalizedString getComment() { + return comment; + } + + public void setComment(LocalizedString comment) { + this.comment = comment; + } + + public CharacterString getLocation() { + return location; + } + + public void setLocation(CharacterString location) { + this.location = location; + } + + public Time getTimestamp() { + return timestamp; + } + + public void setTimestamp(Time timestamp) { + this.timestamp = timestamp; + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CompletionStatus.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CompletionStatus.java new file mode 100644 index 00000000..87eca7b4 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CompletionStatus.java @@ -0,0 +1,66 @@ +package com.xboe.module.scorm.rte.model; + +import java.math.BigDecimal; + +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class CompletionStatus implements TerminalDataType { + + private State completionStatus; + private CMI containerCMI; + + public CompletionStatus(CMI containerCMI) { + this.completionStatus = new State(new String[]{"completed", "incomplete", "not_attempted", "unknown"}); + this.completionStatus.setValue("unknown"); + this.containerCMI = containerCMI; + } + + @Override + public ScormResult set(String value) { + return completionStatus.set(value); + } + + @Override + public ScormResult get() { + String evaluateValue = evaluate(); + if (evaluateValue == null) { + return completionStatus.get(); + } else { + return new ScormResult(evaluateValue, ScormError.E_0); + } + } + + private String evaluate() { + BigDecimal completionThreshold = containerCMI.getCompletionThreshold().getCompletionThreshold().getValue(); + BigDecimal progressMeasure = containerCMI.getProgressMeasure().getProgressMeasure().getValue(); + String completionStatus = this.completionStatus.getValue(); + if (completionThreshold == null) { + if (completionStatus == null) { + return "unknown"; + } else { + return null; + } + } else { + if (progressMeasure != null) { + if (progressMeasure.compareTo(completionThreshold) < 0) { + return "incomplete"; + } else { + return "completed"; + } + } else { + return "unknown"; // #9 + } + } + } + + public State getCompletionStatus() { + return completionStatus; + } + + public void setCompletionStatus(State completionStatus) { + this.completionStatus = completionStatus; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CompletionThreshold.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CompletionThreshold.java new file mode 100644 index 00000000..e2c11114 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CompletionThreshold.java @@ -0,0 +1,45 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.Real7WithRange; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class CompletionThreshold implements TerminalDataType { + + private Real7WithRange completionThreshold; + + public CompletionThreshold() { + completionThreshold = new Real7WithRange(0, 1); + registerHandler(); + } + + private void registerHandler() { + completionThreshold.registerGetHandler(context -> { + Real7WithRange r = (Real7WithRange) context; + if (r.getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return completionThreshold.set(value); + } + + @Override + public ScormResult get() { + return completionThreshold.get(); + } + + public Real7WithRange getCompletionThreshold() { + return completionThreshold; + } + + public void setCompletionThreshold(Real7WithRange completionThreshold) { + this.completionThreshold = completionThreshold; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CorrectResponses.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CorrectResponses.java new file mode 100644 index 00000000..ef0bf0bb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/CorrectResponses.java @@ -0,0 +1,90 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.AbstractCollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Int; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class CorrectResponses extends AbstractCollectionDataType<CorrectResponses.Instance> { + + @Meta(value = "_count", writable = false) + private Int count; + + private Interactions.Instance container; + + public CorrectResponses(Interactions.Instance container) { + this.count = new Int(0); + this.container = container; + registerHandler(); + } + + private void registerHandler() { + count.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + protected Instance newInstance() { + return new Instance(container); + } + + @Override + protected void addCount() { + count.setValue(count.getValue() + 1); + } + + public static class Instance implements GeneralDataType { + + @Meta("pattern") + private Pattern pattern; + + private Interactions.Instance superContainer; + + public Instance(Interactions.Instance superContainer) { + this.superContainer = superContainer; + this.pattern = new Pattern(superContainer); + registerHandler(); + } + + private void registerHandler() { + pattern.registerGetHandler(context -> { + if (((Pattern) context).getPattern() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (superContainer.getId().getValue() == null || + superContainer.getType().getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + } + + public Pattern getPattern() { + return pattern; + } + + public void setPattern(Pattern pattern) { + this.pattern = pattern; + } + + public Interactions.Instance getSuperContainer() { + return superContainer; + } + + public void setSuperContainer(Interactions.Instance superContainer) { + this.superContainer = superContainer; + } + } + + public Int getCount() { + return count; + } + + public void setCount(Int count) { + this.count = count; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Credit.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Credit.java new file mode 100644 index 00000000..3fbb5027 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Credit.java @@ -0,0 +1,39 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Credit implements TerminalDataType { + + private State credit; + + public Credit() { + credit = new State(new String[]{"credit", "no_credit"}); + credit.setValue("credit"); + registerHandler(); + } + + private void registerHandler() { + credit.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return credit.set(value); + } + + @Override + public ScormResult get() { + return credit.get(); + } + + public State getCredit() { + return credit; + } + + public void setCredit(State credit) { + this.credit = credit; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Data.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Data.java new file mode 100644 index 00000000..be2bb8c1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Data.java @@ -0,0 +1,131 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.AbstractCollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Int; +import com.xboe.module.scorm.rte.model.datatype.LongIdentifier; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Data extends AbstractCollectionDataType<Data.Instance> { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta(value = "_count", writable = false) + private Int count; + + public Data() { + this.children = new CharacterString("id,store"); + this.count = new Int(0); + registerHandler(); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + count.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + protected Instance newInstance() { + return new Instance(); + } + + @Override + protected void addCount() { + count.setValue(count.getValue() + 1); + } + + public static class Instance implements GeneralDataType { + + @Meta(value = "id", writable = false) + private LongIdentifier id; + + @Meta("store") + private CharacterString store; + + private boolean readSharedData; + private boolean writeSharedData; + + public Instance() { + this.id = new LongIdentifier(); + this.store = new CharacterString(); + registerHandler(); + } + + private void registerHandler() { + id.registerSetHandler(new ReadOnlyHandler()); + + store.registerGetHandler(context -> { + if (((CharacterString) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + if (!readSharedData) { + return new ScormResult("", ScormError.E_405); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + if (!writeSharedData) { + return new ScormResult("false", ScormError.E_404); + } + return new ScormResult("true", ScormError.E_0); + }); + + } + + public LongIdentifier getId() { + return id; + } + + public void setId(LongIdentifier id) { + this.id = id; + } + + public CharacterString getStore() { + return store; + } + + public void setStore(CharacterString store) { + this.store = store; + } + + public boolean isReadSharedData() { + return readSharedData; + } + + public void setReadSharedData(boolean readSharedData) { + this.readSharedData = readSharedData; + } + + public boolean isWriteSharedData() { + return writeSharedData; + } + + public void setWriteSharedData(boolean writeSharedData) { + this.writeSharedData = writeSharedData; + } + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public Int getCount() { + return count; + } + + public void setCount(Int count) { + this.count = count; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Entry.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Entry.java new file mode 100644 index 00000000..9b575107 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Entry.java @@ -0,0 +1,38 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Entry implements TerminalDataType { + + private State entry; + + public Entry() { + entry = new State(new String[]{"ab_initio","resume",""}); + registerHandler(); + } + + private void registerHandler() { + entry.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return entry.set(value); + } + + @Override + public ScormResult get() { + return entry.get(); + } + + public State getEntry() { + return entry; + } + + public void setEntry(State entry) { + this.entry = entry; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Exit.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Exit.java new file mode 100644 index 00000000..5bfe9c82 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Exit.java @@ -0,0 +1,39 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.handler.WriteOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Exit implements TerminalDataType { + + private State exit; + + public Exit() { + this.exit = new State(new String[]{"timeout", "suspend", "logout", "normal", ""}); + this.exit.setValue(""); + registerHandler(); + } + + private void registerHandler() { + exit.registerGetHandler(new WriteOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return exit.set(value); + } + + @Override + public ScormResult get() { + return exit.get(); + } + + public State getExit() { + return exit; + } + + public void setExit(State exit) { + this.exit = exit; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Interactions.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Interactions.java new file mode 100644 index 00000000..487d27ad --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Interactions.java @@ -0,0 +1,278 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.AbstractCollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Int; +import com.xboe.module.scorm.rte.model.datatype.LocalizedString; +import com.xboe.module.scorm.rte.model.datatype.LongIdentifier; +import com.xboe.module.scorm.rte.model.datatype.Real7; +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.Time; +import com.xboe.module.scorm.rte.model.datatype.TimeInterval; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Interactions extends AbstractCollectionDataType<Interactions.Instance> { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta(value = "_count", writable = false) + private Int count; + + public Interactions() { + children = new CharacterString("id,type,objectives,timestamp,correct_responses,weighting,learner_response," + + "result,latency,description"); + count = new Int(0); + registerHandler(); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + count.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + protected Instance newInstance() { + return new Instance(); + } + + @Override + protected void addCount() { + count.setValue(count.getValue() + 1); + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public Int getCount() { + return count; + } + + public void setCount(Int count) { + this.count = count; + } + + public static class Instance implements GeneralDataType { + + @Meta("id") + private LongIdentifier id; + + @Meta("type") + private State type; + + @Meta("objectives") + private InteractionsObjectives objectives; + + @Meta("timestamp") + private Time timestamp; + + @Meta("correct_responses") + private CorrectResponses correctResponses; + + @Meta("weighting") + private Real7 weighting; + + @Meta("learner_response") + private LearnerResponse learnerResponse; + + @Meta("result") + private Result result; + + @Meta("latency") + private TimeInterval latency; + + @Meta("description") + private LocalizedString description; + + public Instance() { + this.id = new LongIdentifier(); + this.type = new State(new String[]{"true_false", "multiple_choice", "fill_in", "long_fill_in", "matching", + "performance", "sequencing", "likert", "numeric", "other"}); + this.objectives = new InteractionsObjectives(this); + this.timestamp = new Time(); + this.correctResponses = new CorrectResponses(this); + this.weighting = new Real7(); + this.learnerResponse = new LearnerResponse(this); + this.result = new Result(); + this.description = new LocalizedString(); + registerHandler(); + } + + private void registerHandler() { + type.registerGetHandler(context -> { + if (((State) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + timestamp.registerGetHandler(context -> { + if (((Time) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + weighting.registerGetHandler(context -> { + if (((Real7) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + learnerResponse.registerGetHandler(context -> { + if (((LearnerResponse) context).getLearnerResponse() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null || type.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + result.registerGetHandler(context -> { + if (((Result) context).getResult() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + latency.registerGetHandler(context -> { + if (((TimeInterval) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + description.registerGetHandler(context -> { + if (((LocalizedString) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + } + + public LongIdentifier getId() { + return id; + } + + public void setId(LongIdentifier id) { + this.id = id; + } + + public State getType() { + return type; + } + + public void setType(State type) { + this.type = type; + } + + public InteractionsObjectives getObjectives() { + return objectives; + } + + public void setObjectives(InteractionsObjectives objectives) { + this.objectives = objectives; + } + + public Time getTimestamp() { + return timestamp; + } + + public void setTimestamp(Time timestamp) { + this.timestamp = timestamp; + } + + public CorrectResponses getCorrectResponses() { + return correctResponses; + } + + public void setCorrectResponses(CorrectResponses correctResponses) { + this.correctResponses = correctResponses; + } + + public Real7 getWeighting() { + return weighting; + } + + public void setWeighting(Real7 weighting) { + this.weighting = weighting; + } + + public LearnerResponse getLearnerResponse() { + return learnerResponse; + } + + public void setLearnerResponse(LearnerResponse learnerResponse) { + this.learnerResponse = learnerResponse; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + public TimeInterval getLatency() { + return latency; + } + + public void setLatency(TimeInterval latency) { + this.latency = latency; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/InteractionsObjectives.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/InteractionsObjectives.java new file mode 100644 index 00000000..0df094b5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/InteractionsObjectives.java @@ -0,0 +1,90 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.AbstractCollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Int; +import com.xboe.module.scorm.rte.model.datatype.LongIdentifier; +import com.xboe.module.scorm.rte.model.error.Diagnostic; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class InteractionsObjectives extends AbstractCollectionDataType<InteractionsObjectives.Instance> { + + @Meta(value = "_count", writable = false) + private Int count; + + private Interactions.Instance container; + + public InteractionsObjectives(Interactions.Instance container) { + this.count = new Int(0); + this.container = container; + registerHandler(); + } + + private void registerHandler() { + count.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + protected Instance newInstance() { + return new Instance(container); + } + + @Override + protected void addCount() { + count.setValue(count.getValue() + 1); + } + + public Int getCount() { + return count; + } + + public void setCount(Int count) { + this.count = count; + } + + public static class Instance implements GeneralDataType { + + @Meta("id") + private LongIdentifier id; + + private Interactions.Instance superContainer; + + public Instance(Interactions.Instance superContainer) { + this.id = new LongIdentifier(); + this.superContainer = superContainer; + registerHandler(); + } + + private void registerHandler() { + id.registerSetHandler((context, value) -> { + if (superContainer.getId().getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + boolean isUnique = true; + for (Instance instance : superContainer.getObjectives().getInstances()) { + if (instance.getId().getValue().equals(value)) { + isUnique = false; + break; + } + } + if (!isUnique) { + return new ScormResult("false", ScormError.E_351, + Diagnostic.UNIQUE_IDENTIFIER_CONSTRAINT_VIOLATED); + } + return new ScormResult("true", ScormError.E_0); + }); + } + + public LongIdentifier getId() { + return id; + } + + public void setId(LongIdentifier id) { + this.id = id; + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Language.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Language.java new file mode 100644 index 00000000..d1499855 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Language.java @@ -0,0 +1,38 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Language implements TerminalDataType { + + private String language; + + public Language() { + language = ""; + } + + @Override + public ScormResult set(String value) { + if (!CommonUtils.isLegalLanguage(value) || !"".equals(value)) { + return new ScormResult("false", ScormError.E_406, + "parameter should be empty string or a legal language code"); + } + this.language = value; + return new ScormResult("true", ScormError.E_0); + } + + @Override + public ScormResult get() { + return new ScormResult(language, ScormError.E_0); + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LaunchData.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LaunchData.java new file mode 100644 index 00000000..c985acbb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LaunchData.java @@ -0,0 +1,45 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class LaunchData implements TerminalDataType { + + private CharacterString launchData; + + public LaunchData() { + this.launchData = new CharacterString(); + registerHandler(); + } + + private void registerHandler() { + launchData.registerGetHandler(context -> { + if (((CharacterString) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler(new ReadOnlyHandler()); + + } + + @Override + public ScormResult set(String value) { + return this.launchData.set(value); + } + + @Override + public ScormResult get() { + return this.launchData.get(); + } + + public CharacterString getLaunchData() { + return launchData; + } + + public void setLaunchData(CharacterString launchData) { + this.launchData = launchData; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerId.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerId.java new file mode 100644 index 00000000..7ce93188 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerId.java @@ -0,0 +1,38 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.LongIdentifier; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class LearnerId implements TerminalDataType { + + private LongIdentifier learnerId; + + public LearnerId() { + this.learnerId = new LongIdentifier(); + registerHandler(); + } + + private void registerHandler() { + learnerId.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return this.learnerId.set(value); + } + + @Override + public ScormResult get() { + return this.learnerId.get(); + } + + public LongIdentifier getLearnerId() { + return learnerId; + } + + public void setLearnerId(LongIdentifier learnerId) { + this.learnerId = learnerId; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerName.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerName.java new file mode 100644 index 00000000..9e6d1a29 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerName.java @@ -0,0 +1,38 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.LocalizedString; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class LearnerName implements TerminalDataType { + + private LocalizedString learnerName; + + public LearnerName() { + this.learnerName = new LocalizedString(); + registerHandler(); + } + + private void registerHandler() { + learnerName.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return this.learnerName.set(value); + } + + @Override + public ScormResult get() { + return this.learnerName.get(); + } + + public LocalizedString getLearnerName() { + return learnerName; + } + + public void setLearnerName(LocalizedString learnerName) { + this.learnerName = learnerName; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerPreference.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerPreference.java new file mode 100644 index 00000000..2feecf9e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerPreference.java @@ -0,0 +1,83 @@ +package com.xboe.module.scorm.rte.model; + +import java.math.BigDecimal; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Real7WithMin; +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; + +public class LearnerPreference implements GeneralDataType { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta("audio_level") + private AudioLevel audioLevel; + + @Meta("language") + private Language language; + + @Meta("delivery_speed") + private Real7WithMin deliverySpeed; + + @Meta("audio_captioning") + private State audioCaptioning; + + public LearnerPreference() { + this.children = new CharacterString("audio_level,language,delivery_speed,audio_captioning"); + this.audioLevel = new AudioLevel(); + this.language = new Language(); + this.deliverySpeed = new Real7WithMin(0); + this.deliverySpeed.setValue(new BigDecimal(1).setScale(7, BigDecimal.ROUND_HALF_UP)); + this.audioCaptioning = new State(new String[]{"-1", "0", "1"}); + this.audioCaptioning.setValue("0"); + registerHandler(); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public AudioLevel getAudioLevel() { + return audioLevel; + } + + public void setAudioLevel(AudioLevel audioLevel) { + this.audioLevel = audioLevel; + } + + public Language getLanguage() { + return language; + } + + public void setLanguage(Language language) { + this.language = language; + } + + public Real7WithMin getDeliverySpeed() { + return deliverySpeed; + } + + public void setDeliverySpeed(Real7WithMin deliverySpeed) { + this.deliverySpeed = deliverySpeed; + } + + public State getAudioCaptioning() { + return audioCaptioning; + } + + public void setAudioCaptioning(State audioCaptioning) { + this.audioCaptioning = audioCaptioning; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerResponse.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerResponse.java new file mode 100644 index 00000000..dd278ff9 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/LearnerResponse.java @@ -0,0 +1,299 @@ +package com.xboe.module.scorm.rte.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.Interactions.Instance; +import com.xboe.module.scorm.rte.model.datatype.AbstractTerminalDataType; +import com.xboe.module.scorm.rte.model.datatype.LocalizedString; +import com.xboe.module.scorm.rte.model.datatype.Real7; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +// TODO 4.2.9.2 page 133 +public class LearnerResponse extends AbstractTerminalDataType { + + private Object learnerResponseObject; + private String learnerResponse; + + private Interactions.Instance container; + + public LearnerResponse(Instance container) { + this.container = container; + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + if (!set0(value)) { + return new ScormResult("false", ScormError.E_406); + } + this.learnerResponse = value; + return scormResult; + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(learnerResponse); + } + + private boolean set0(String value) { + String type = container.getType().getValue(); + if (StringUtils.isBlank(type)) { + return true; + } + if (StringUtils.isBlank(value)) { + return false; + } + switch (type) { + case "true_false": + return setForTrueFalse(value); + case "multiple_choice": + return setForMultipleChoice(value); + case "fill_in": + return setForFillIn(value); + case "long_fill_in": + return setForLongFillIn(value); + case "likert": + return setForLikert(value); + case "matching": + return setForMatching(value); + case "performance": + return setForPerformance(value); + case "sequencing": + return setForSequencing(value); + case "numeric": + return setForNumeric(value); + case "other": + return setForOther(value); + } + return true; + } + + private boolean setForOther(String value) { + this.learnerResponse = value; + this.learnerResponseObject = value; + return true; + } + + private boolean setForNumeric(String value) { + Real7 numericObject = new Real7(value); + + if (numericObject.getValue() == null) { + return false; + } + + this.learnerResponseObject = numericObject; + this.learnerResponse = value; + return true; + } + + private boolean setForSequencing(String value) { + String[] records = value.split("\\[,]"); + List<String> sequencing = new ArrayList<>(records.length); + for (String record : records) { + if (!CommonUtils.isLegalURI(record)) { + return false; + } + sequencing.add(record); + } + this.learnerResponse = value; + this.learnerResponseObject = sequencing; + return true; + } + + private boolean setForPerformance(String value) { + List<Step> performanceObject = new ArrayList<>(); + String[] steps = value.split("\\[,]"); + for (String step : steps) { + String[] content = step.split("\\[\\.]"); + if (content.length != 2) { + return false; + } + if (!CommonUtils.isLegalURI(content[0])) { + return false; + } + Step stepObject = new Step(); + stepObject.name = content[0]; + stepObject.answer = content[1]; + + Real7 ans = new Real7(content[1]); + + if (ans.getValue() == null) { + stepObject.answerType = "literal"; + stepObject.answerObject = content[1]; + } else { + stepObject.answerType = "numeric"; + stepObject.answerObject = ans; + } + + performanceObject.add(stepObject); + } + this.learnerResponseObject = performanceObject; + this.learnerResponse = value; + return true; + } + + private boolean setForMatching(String value) { + MatchingObject matchingObject = new MatchingObject(); + String[] records = value.split("\\[,]"); + for (String record : records) { + String[] content = record.split("\\[\\.]"); + if (content.length != 2) { + return false; + } + if (!CommonUtils.isLegalURI(content[0]) || !CommonUtils.isLegalURI(content[1])) { + return false; + } + matchingObject.records.add(content); + } + this.learnerResponse = value; + this.learnerResponseObject = matchingObject; + return true; + } + + private boolean setForLikert(String value) { + if (CommonUtils.isLegalURI(value)) { + this.learnerResponseObject = value; + this.learnerResponse = value; + return true; + } else { + return false; + } + } + + private boolean setForLongFillIn(String value) { + LocalizedString matchText = new LocalizedString(value); + if (matchText.getValue() != null) { + this.learnerResponseObject = matchText; + this.learnerResponse = value; + return true; + } else { + return false; + } + } + + private boolean setForFillIn(String value) { + String[] matchTexts = value.split("\\[,]"); + List<LocalizedString> fillInObject = new ArrayList<>(); + for (String matchText : matchTexts) { + LocalizedString mt = new LocalizedString(matchText); + if (mt.getValue() == null) { + return false; + } + fillInObject.add(mt); + } + this.learnerResponseObject = fillInObject; + this.learnerResponse = value; + return true; + } + + private boolean setForMultipleChoice(String value) { + String[] content = value.split("\\[,]"); + boolean success = true; + List<String> shortIDList = new ArrayList<>(content.length); + for (String s : content) { + if (!CommonUtils.isLegalURI(s)) { + success = false; + break; + } + shortIDList.add(s); + } + if (success) { + this.learnerResponseObject = shortIDList; + this.learnerResponse = value; + return true; + } else { + return false; + } + } + + private boolean setForTrueFalse(String value) { + if (StringUtils.equals(value, "true") || StringUtils.equals(value, "false")) { + this.learnerResponseObject = value; + this.learnerResponse = value; + return true; + } else { + return false; + } + } + + public String getLearnerResponse() { + return learnerResponse; + } + + public void setLearnerResponse(String learnerResponse) { + set0(learnerResponse); + } + + public Object getLearnerResponseObject() { + return learnerResponseObject; + } + + public static class MatchingObject { + List<String[]> records; + + public MatchingObject() { + records = new ArrayList<>(); + } + + public List<String[]> getRecords() { + return records; + } + + public void setRecords(List<String[]> records) { + this.records = records; + } + } + + public static class Step { + private String name; + private String answerType; + private Object answerObject; + private String answer; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAnswerType() { + return answerType; + } + + public void setAnswerType(String answerType) { + this.answerType = answerType; + } + + public Object getAnswerObject() { + return answerObject; + } + + public void setAnswerObject(Object answerObject) { + this.answerObject = answerObject; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + } + + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Location.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Location.java new file mode 100644 index 00000000..60c19cec --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Location.java @@ -0,0 +1,44 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Location implements TerminalDataType { + + private CharacterString location; + + public Location() { + this.location = new CharacterString(); + this.location.setValue(""); + registerHandler(); + } + + private void registerHandler() { + location.registerGetHandler(context -> { + if (((CharacterString) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + } + + @Override + public ScormResult set(String value) { + return this.location.set(value); + } + + @Override + public ScormResult get() { + return this.location.get(); + } + + public CharacterString getLocation() { + return location; + } + + public void setLocation(CharacterString location) { + this.location = location; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/MaximumTimeAllowed.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/MaximumTimeAllowed.java new file mode 100644 index 00000000..abc3281f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/MaximumTimeAllowed.java @@ -0,0 +1,44 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.datatype.TimeInterval; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class MaximumTimeAllowed implements TerminalDataType { + + private TimeInterval maximumTimeAllowed; + + public MaximumTimeAllowed() { + this.maximumTimeAllowed = new TimeInterval(); + registerHandler(); + } + + private void registerHandler() { + maximumTimeAllowed.registerGetHandler(context -> { + if (((TimeInterval) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return this.maximumTimeAllowed.set(value); + } + + @Override + public ScormResult get() { + return this.maximumTimeAllowed.get(); + } + + public TimeInterval getMaximumTimeAllowed() { + return maximumTimeAllowed; + } + + public void setMaximumTimeAllowed(TimeInterval maximumTimeAllowed) { + this.maximumTimeAllowed = maximumTimeAllowed; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Mode.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Mode.java new file mode 100644 index 00000000..5b4a47cc --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Mode.java @@ -0,0 +1,40 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +// TODO RTE-4-93 +public class Mode implements TerminalDataType { + + private State mode; + + public Mode() { + this.mode = new State(new String[]{"browse", "normal", "review"}); + this.mode.setValue("normal"); + registerHandler(); + } + + private void registerHandler() { + mode.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return this.mode.set(value); + } + + @Override + public ScormResult get() { + return this.mode.get(); + } + + public State getMode() { + return mode; + } + + public void setMode(State mode) { + this.mode = mode; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Nav.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Nav.java new file mode 100644 index 00000000..a9923869 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Nav.java @@ -0,0 +1,36 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; + +public class Nav implements GeneralDataType { + + @Meta("request") + private Request request; + + @Meta("request_valid") + private RequestValid requestValid; + + public Nav() { + this.request = new Request(); + this.requestValid = new RequestValid(); + } + + public Request getRequest() { + return request; + } + + public Nav setRequest(Request request) { + this.request = request; + return this; + } + + public RequestValid getRequestValid() { + return requestValid; + } + + public Nav setRequestValid(RequestValid requestValid) { + this.requestValid = requestValid; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Objectives.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Objectives.java new file mode 100644 index 00000000..44ac7abf --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Objectives.java @@ -0,0 +1,212 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.AbstractCollectionDataType; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Int; +import com.xboe.module.scorm.rte.model.datatype.LocalizedString; +import com.xboe.module.scorm.rte.model.datatype.LongIdentifier; +import com.xboe.module.scorm.rte.model.datatype.Real7WithRange; +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.error.Diagnostic; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Objectives extends AbstractCollectionDataType<Objectives.Instance> { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta(value = "_count", writable = false) + private Int count; + + public Objectives() { + this.children = new CharacterString("id,score,success_status,completion_status,progress_measure,description"); + this.count = new Int(0); + registerHandler(); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + count.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + protected Instance newInstance() { + return new Instance(this); + } + + @Override + protected void addCount() { + count.setValue(count.getValue() + 1); + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public Int getCount() { + return count; + } + + public void setCount(Int count) { + this.count = count; + } + + public static class Instance implements GeneralDataType { + + @Meta("id") + private LongIdentifier id; + + @Meta("score") + private ObjectivesScore score; + + @Meta("success_status") + private State successStatus; + + @Meta("completion_status") + private State completionStatus; + + @Meta("progress_measure") + private Real7WithRange progressMeasure; + + @Meta("description") + private LocalizedString description; + + private Objectives container; + + public Instance(Objectives container) { + this.id = new LongIdentifier(); + this.score = new ObjectivesScore(this); + this.successStatus = new State(new String[]{"passed", "failed", "unknown"}); + this.successStatus.setValue("unknown"); + this.completionStatus = new State(new String[]{"completed", "incomplete", "not_attempted", "unknown"}); + this.completionStatus.setValue("unknown"); + this.progressMeasure = new Real7WithRange(0, 1); + this.description = new LocalizedString(); + this.container = container; + registerHandler(); + } + + private void registerHandler() { + id.registerSetHandler((context, value) -> { + String nowValue = ((LongIdentifier) context).getValue(); + if (nowValue != null) { + if (nowValue.equals(value)) { + return new ScormResult("true", ScormError.E_0); + } else { + return new ScormResult("false", ScormError.E_351, + Diagnostic.IDENTIFIER_VALUE_CAN_ONLY_BE_SET_ONCE); + } + } + boolean isUnique = true; + for (Instance instance : container.getInstances()) { + if (instance.getId().getValue().equals(value)) { + isUnique = false; + break; + } + } + if (!isUnique) { + return new ScormResult("false", ScormError.E_351, + Diagnostic.UNIQUE_IDENTIFIER_CONSTRAINT_VIOLATED); + } + return new ScormResult("true", ScormError.E_0); + }); + + successStatus.registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + completionStatus.registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + progressMeasure.registerGetHandler(context -> { + if (((Real7WithRange) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + description.registerGetHandler(context -> { + if (((LocalizedString) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (id.getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + } + + public LongIdentifier getId() { + return id; + } + + public void setId(LongIdentifier id) { + this.id = id; + } + + public ObjectivesScore getScore() { + return score; + } + + public void setScore(ObjectivesScore score) { + this.score = score; + } + + public State getSuccessStatus() { + return successStatus; + } + + public void setSuccessStatus(State successStatus) { + this.successStatus = successStatus; + } + + public State getCompletionStatus() { + return completionStatus; + } + + public void setCompletionStatus(State completionStatus) { + this.completionStatus = completionStatus; + } + + public Real7WithRange getProgressMeasure() { + return progressMeasure; + } + + public void setProgressMeasure(Real7WithRange progressMeasure) { + this.progressMeasure = progressMeasure; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ObjectivesScore.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ObjectivesScore.java new file mode 100644 index 00000000..5f0824d1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ObjectivesScore.java @@ -0,0 +1,133 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Real7; +import com.xboe.module.scorm.rte.model.datatype.Real7WithRange; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class ObjectivesScore implements GeneralDataType { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta("scaled") + private Real7WithRange scaled; + + @Meta("raw") + private Real7 raw; + + @Meta("min") + private Real7 min; + + @Meta("max") + private Real7 max; + + private Objectives.Instance container; + + public ObjectivesScore(Objectives.Instance container) { + this.children = new CharacterString("scaled,raw,min,max"); + this.scaled = new Real7WithRange(-1, 1); + this.raw = new Real7(); + this.min = new Real7(); + this.max = new Real7(); + this.container = container; + registerHandler(); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + + scaled.registerGetHandler(context -> { + if (((Real7WithRange) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (container.getId().getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + raw.registerGetHandler(context -> { + if (((Real7) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (container.getId().getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + min.registerGetHandler(context -> { + if (((Real7) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (container.getId().getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + max.registerGetHandler(context -> { + if (((Real7) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler((context, value) -> { + if (container.getId().getValue() == null) { + return new ScormResult("false", ScormError.E_408); + } + return new ScormResult("true", ScormError.E_0); + }); + + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public Real7WithRange getScaled() { + return scaled; + } + + public void setScaled(Real7WithRange scaled) { + this.scaled = scaled; + } + + public Real7 getRaw() { + return raw; + } + + public void setRaw(Real7 raw) { + this.raw = raw; + } + + public Real7 getMin() { + return min; + } + + public void setMin(Real7 min) { + this.min = min; + } + + public Real7 getMax() { + return max; + } + + public void setMax(Real7 max) { + this.max = max; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Pattern.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Pattern.java new file mode 100644 index 00000000..2901b63c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Pattern.java @@ -0,0 +1,524 @@ +package com.xboe.module.scorm.rte.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.Interactions.Instance; +import com.xboe.module.scorm.rte.model.Pattern.PerformanceObject.Step; +import com.xboe.module.scorm.rte.model.datatype.AbstractTerminalDataType; +import com.xboe.module.scorm.rte.model.datatype.LocalizedString; +import com.xboe.module.scorm.rte.model.datatype.Real7; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Pattern extends AbstractTerminalDataType { + + private static final String CASE_TRUE = "{case_matters=true}"; + private static final String CASE_FALSE = "{case_matters=false}"; + private static final String ORDER_TRUE = "{order_matters=true}"; + private static final String ORDER_FALSE = "{order_matters=false}"; + + private Object patternObject; + private String pattern; + + private Interactions.Instance superSuperContainer; + + public Pattern(Instance superSuperContainer) { + this.superSuperContainer = superSuperContainer; + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + boolean isUnique = true; + for (CorrectResponses.Instance instance : superSuperContainer.getCorrectResponses().getInstances()) { + if (StringUtils.equals(instance.getPattern().getPattern(), value)) { + isUnique = false; + break; + } + } + if (!isUnique) { + return new ScormResult("false", ScormError.E_351); + } + if (!set0(value)) { + return new ScormResult("false", ScormError.E_406); + } + return scormResult; + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(pattern); + } + + private boolean set0(String value) { + String type = superSuperContainer.getType().getValue(); + if (StringUtils.isBlank(type)) { + return true; + } + if (StringUtils.isBlank(value)) { + return false; + } + switch (type) { + case "true_false": + return setForTrueFalse(value); + case "multiple_choice": + return setForMultipleChoice(value); + case "fill_in": + return setForFillIn(value); + case "long_fill_in": + return setForLongFillIn(value); + case "likert": + return setForLikert(value); + case "matching": + return setForMatching(value); + case "performance": + return setForPerformance(value); + case "sequencing": + return setForSequencing(value); + case "numeric": + return setForNumeric(value); + case "other": + return setForOther(value); + } + return true; + } + + private boolean setForOther(String value) { + this.pattern = value; + this.patternObject = value; + return true; + } + + private boolean setForNumeric(String value) { + String[] ans = value.split("\\[:]"); + NumericObject numericObject = new NumericObject(); + if (ans.length > 2) { + return false; + } else if (ans.length == 1) { + Real7 m = new Real7(ans[0]); + if (m.getValue() == null) { + return false; + } + if (value.startsWith("[:]")) { + numericObject.max = m; + } else { + numericObject.min = m; + } + } else if (ans.length == 2) { + Real7 min = new Real7(ans[0]); + Real7 max = new Real7(ans[1]); + if (min.getValue() == null || max.getValue() == null) { + return false; + } + numericObject.min = min; + numericObject.max = max; + } + this.patternObject = numericObject; + this.pattern = value; + return true; + } + + private boolean setForSequencing(String value) { + String[] records = value.split("\\[,]"); + List<String> sequencing = new ArrayList<>(records.length); + for (String record : records) { + if (!CommonUtils.isLegalURI(record)) { + return false; + } + sequencing.add(record); + } + this.pattern = value; + this.patternObject = sequencing; + return true; + } + + private boolean setForPerformance(String value) { + PerformanceObject performanceObject = new PerformanceObject(); + String tmp = value; + boolean flag = false; + if (tmp.startsWith(ORDER_TRUE)) { + performanceObject.orderMatters = true; + flag = true; + } else if (tmp.startsWith(ORDER_FALSE)) { + performanceObject.orderMatters = false; + flag = true; + } + if (flag) { + tmp = tmp.substring(0, tmp.indexOf("}") + 1); + } + String[] steps = tmp.split("\\[,]"); + for (String step : steps) { + String[] content = step.split("\\[\\.]"); + if (content.length != 2) { + return false; + } + if (!CommonUtils.isLegalURI(content[0])) { + return false; + } + Step stepObject = new Step(); + stepObject.name = content[0]; + stepObject.answer = content[1]; + if (!content[1].contains("[:]")) { + stepObject.answerType = "literal"; + stepObject.answerObject = content[1]; + } else { + String[] ans = content[1].split("\\[:]"); + NumericObject numericObject = new NumericObject(); + if (ans.length > 2) { + return false; + } else if (ans.length == 1) { + Real7 m = new Real7(ans[0]); + if (m.getValue() == null) { + return false; + } + if (content[1].startsWith("[:]")) { + numericObject.max = m; + } else { + numericObject.min = m; + } + } else if (ans.length == 2) { + Real7 min = new Real7(ans[0]); + Real7 max = new Real7(ans[1]); + if (min.getValue() == null || max.getValue() == null) { + return false; + } + numericObject.min = min; + numericObject.max = max; + } + stepObject.answerType = "numeric"; + stepObject.answerObject = numericObject; + } + performanceObject.steps.add(stepObject); + } + this.patternObject = performanceObject; + this.pattern = value; + return true; + } + + private boolean setForMatching(String value) { + MatchingObject matchingObject = new MatchingObject(); + String[] records = value.split("\\[,]"); + for (String record : records) { + String[] content = record.split("\\[\\.]"); + if (content.length != 2) { + return false; + } + if (!CommonUtils.isLegalURI(content[0]) || !CommonUtils.isLegalURI(content[1])) { + return false; + } + matchingObject.records.add(content); + } + this.pattern = value; + this.patternObject = matchingObject; + return true; + } + + private boolean setForLikert(String value) { + if (CommonUtils.isLegalURI(value)) { + this.patternObject = value; + this.pattern = value; + return true; + } else { + return false; + } + } + + private boolean setForLongFillIn(String value) { + LongFillInObject longFillInObject = new LongFillInObject(); + String tmp = value; + boolean flag = false; + if (tmp.startsWith(CASE_TRUE)) { + longFillInObject.caseMatters = true; + flag = true; + } else if (tmp.startsWith(CASE_FALSE)) { + longFillInObject.caseMatters = false; + flag = true; + } + if (flag) { + tmp = tmp.substring(0, tmp.indexOf("}") + 1); + } + LocalizedString matchText = new LocalizedString(tmp); + if (matchText.getValue() != null) { + longFillInObject.matchText = matchText; + this.patternObject = longFillInObject; + this.pattern = value; + return true; + } else { + return false; + } + } + + private boolean setForFillIn(String value) { + FillInObject fillInObject = new FillInObject(); + String tmp = value; + boolean flag = false; + if (tmp.startsWith(CASE_TRUE)) { + fillInObject.caseMatters = true; + flag = true; + } else if (tmp.startsWith(CASE_FALSE)) { + fillInObject.caseMatters = false; + flag = true; + } else if (tmp.startsWith(ORDER_TRUE)) { + fillInObject.orderMatters = true; + flag = true; + } else if (tmp.startsWith(ORDER_FALSE)) { + fillInObject.orderMatters = false; + flag = true; + } + if (flag) { + tmp = tmp.substring(0, tmp.indexOf("}") + 1); + flag = false; + if (tmp.startsWith(CASE_TRUE)) { + fillInObject.caseMatters = true; + flag = true; + } else if (tmp.startsWith(CASE_FALSE)) { + fillInObject.caseMatters = false; + flag = true; + } else if (tmp.startsWith(ORDER_TRUE)) { + fillInObject.orderMatters = true; + flag = true; + } else if (tmp.startsWith(ORDER_FALSE)) { + fillInObject.orderMatters = false; + flag = true; + } + if (flag) { + tmp = tmp.substring(0, tmp.indexOf("}") + 1); + } + } + String[] matchTexts = tmp.split("\\[,]"); + flag = true; + for (String matchText : matchTexts) { + LocalizedString mt = new LocalizedString(matchText); + if (mt.getValue() == null) { + flag = false; + break; + } + fillInObject.matchTextList.add(mt); + } + if (flag) { + this.patternObject = fillInObject; + this.pattern = value; + return true; + } else { + return false; + } + } + + private boolean setForMultipleChoice(String value) { + String[] content = value.split("\\[,]"); + boolean success = true; + List<String> shortIDList = new ArrayList<>(content.length); + for (String s : content) { + if (!CommonUtils.isLegalURI(s)) { + success = false; + break; + } + shortIDList.add(s); + } + if (success) { + this.patternObject = shortIDList; + this.pattern = value; + return true; + } else { + return false; + } + } + + private boolean setForTrueFalse(String value) { + if (StringUtils.equals(value, "true") || StringUtils.equals(value, "false")) { + this.patternObject = value; + this.pattern = value; + return true; + } else { + return false; + } + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + set0(pattern); + } + + public Object getPatternObject() { + return patternObject; + } + + public static class FillInObject { + boolean caseMatters; + boolean orderMatters; + private List<LocalizedString> matchTextList; + + public FillInObject() { + caseMatters = false; + orderMatters = true; + matchTextList = new ArrayList<>(); + } + + public boolean isCaseMatters() { + return caseMatters; + } + + public void setCaseMatters(boolean caseMatters) { + this.caseMatters = caseMatters; + } + + public boolean isOrderMatters() { + return orderMatters; + } + + public void setOrderMatters(boolean orderMatters) { + this.orderMatters = orderMatters; + } + + public List<LocalizedString> getMatchTextList() { + return matchTextList; + } + + public void setMatchTextList(List<LocalizedString> matchTextList) { + this.matchTextList = matchTextList; + } + } + + public static class LongFillInObject { + boolean caseMatters; + private LocalizedString matchText; + + public LongFillInObject() { + caseMatters = false; + } + + public boolean isCaseMatters() { + return caseMatters; + } + + public void setCaseMatters(boolean caseMatters) { + this.caseMatters = caseMatters; + } + + public LocalizedString getMatchText() { + return matchText; + } + + public void setMatchText(LocalizedString matchText) { + this.matchText = matchText; + } + } + + public static class MatchingObject { + List<String[]> records; + + public MatchingObject() { + records = new ArrayList<>(); + } + + public List<String[]> getRecords() { + return records; + } + + public void setRecords(List<String[]> records) { + this.records = records; + } + } + + public static class PerformanceObject { + private boolean orderMatters; + private List<Step> steps; + + public PerformanceObject() { + orderMatters = true; + steps = new ArrayList<>(); + } + + public boolean isOrderMatters() { + return orderMatters; + } + + public void setOrderMatters(boolean orderMatters) { + this.orderMatters = orderMatters; + } + + public List<Step> getSteps() { + return steps; + } + + public void setSteps(List<Step> steps) { + this.steps = steps; + } + + public static class Step { + private String name; + private String answerType; + private Object answerObject; + private String answer; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAnswerType() { + return answerType; + } + + public void setAnswerType(String answerType) { + this.answerType = answerType; + } + + public Object getAnswerObject() { + return answerObject; + } + + public void setAnswerObject(Object answerObject) { + this.answerObject = answerObject; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + } + + } + + public static class NumericObject { + private Real7 min; + private Real7 max; + + public Real7 getMin() { + return min; + } + + public void setMin(Real7 min) { + this.min = min; + } + + public Real7 getMax() { + return max; + } + + public void setMax(Real7 max) { + this.max = max; + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ProgressMeasure.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ProgressMeasure.java new file mode 100644 index 00000000..426400a7 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ProgressMeasure.java @@ -0,0 +1,43 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.Real7WithRange; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class ProgressMeasure implements TerminalDataType { + + private Real7WithRange progressMeasure; + + public ProgressMeasure() { + this.progressMeasure = new Real7WithRange(0, 1); + registerHandler(); + } + + private void registerHandler() { + progressMeasure.registerGetHandler(context -> { + if (((Real7WithRange) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + } + + @Override + public ScormResult set(String value) { + return this.progressMeasure.set(value); + } + + @Override + public ScormResult get() { + return this.progressMeasure.get(); + } + + public Real7WithRange getProgressMeasure() { + return progressMeasure; + } + + public void setProgressMeasure(Real7WithRange progressMeasure) { + this.progressMeasure = progressMeasure; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Request.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Request.java new file mode 100644 index 00000000..affab116 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Request.java @@ -0,0 +1,104 @@ +package com.xboe.module.scorm.rte.model; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Request implements TerminalDataType { + + private static final Set<String> requestTable = new HashSet<>(Arrays.asList( + "continue", "previous", "choice", "jump", "exit", "exitAll", + "abandon", "abandonAll", "suspendAll", "_none_")); + + private String value; + + private String request; + + private String target; + + public Request() { + this.value = "_none_"; + this.request = ""; + this.target = ""; + } + + @Override + public ScormResult set(String value) { + if (set0(value)) { + return new ScormResult("true", ScormError.E_0); + } else { + return new ScormResult("false", ScormError.E_406); + } + } + + @Override + public ScormResult get() { + return new ScormResult(value, ScormError.E_0); + } + + + private boolean set0(String value) { + if (StringUtils.isBlank(value)) { + return false; + } + value = value.trim(); + String request; + String target; + if (value.startsWith("{target=")) { + target = value.substring(8, value.indexOf("}")); + request = value.substring(value.indexOf("}") + 1); + } else { + target = ""; + request = value; + } + if (!requestTable.contains(request)) { + return false; + } + if ("choice".equals(request) || "jump".equals(request)) { + if (StringUtils.isBlank(target)) { + return false; + } + } else { + if (StringUtils.isNotBlank(target)) { + return false; + } + } + this.value = value; + this.request = request.trim(); + this.target = target.trim(); + return true; + } + + public String getValue() { + return value; + } + + public Request setValue(String value) { + this.value = value; + return this; + } + + public String getRequest() { + return request; + } + + public Request setRequest(String request) { + this.request = request; + return this; + } + + public String getTarget() { + return target; + } + + public Request setTarget(String target) { + this.target = target; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/RequestValid.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/RequestValid.java new file mode 100644 index 00000000..f444a044 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/RequestValid.java @@ -0,0 +1,38 @@ +package com.xboe.module.scorm.rte.model; + +import java.util.Arrays; +import java.util.HashSet; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.StateReadOnlyMap; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; + +public class RequestValid implements GeneralDataType { + + @Meta("continue") + private State continueValid; + + @Meta("previous") + private State previous; + + @Meta("choice") + private StateReadOnlyMap choice; + + @Meta("jump") + private StateReadOnlyMap jump; + + public RequestValid() { + this.continueValid = new State("unknown", new HashSet<>(Arrays.asList("true", "false", "unknown"))); + this.previous = new State("unknown", new HashSet<>(Arrays.asList("true", "false", "unknown"))); + this.choice = new StateReadOnlyMap("true", "false", "unknown"); + this.jump = new StateReadOnlyMap("true", "false", "unknown"); + registerHandler(); + } + + private void registerHandler() { + continueValid.registerSetHandler(new ReadOnlyHandler()); + previous.registerSetHandler(new ReadOnlyHandler()); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Result.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Result.java new file mode 100644 index 00000000..5da48369 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Result.java @@ -0,0 +1,59 @@ +package com.xboe.module.scorm.rte.model; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.datatype.AbstractTerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Result extends AbstractTerminalDataType { + + private static final Set<String> STATE_TABLE + = new HashSet<>(Arrays.asList("correct", "incorrect", "unanticipated", "neutral")); + + private String result; + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + boolean valid; + try { + Double.parseDouble(value); + valid = true; + } catch (Exception e) { + valid = false; + } + valid |= STATE_TABLE.contains(value); + if (valid) { + this.result = value; + return new ScormResult("true", ScormError.E_0); + } else { + return new ScormResult("false", ScormError.E_406, CommonUtils.format( + "parameter should be a decimal or be one of the following tokens: {}", + (Object) STATE_TABLE.toArray())); + } + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(result); + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/RuntimeData.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/RuntimeData.java new file mode 100644 index 00000000..ccc07ebb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/RuntimeData.java @@ -0,0 +1,34 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; + +public class RuntimeData implements GeneralDataType { + + @Meta("cmi") + private CMI cmi; + + @Meta("adl") + private ADL adl; + + public RuntimeData() { + this.cmi = new CMI(); + this.adl = new ADL(); + } + + public CMI getCmi() { + return cmi; + } + + public void setCmi(CMI cmi) { + this.cmi = cmi; + } + + public ADL getAdl() { + return adl; + } + + public void setAdl(ADL adl) { + this.adl = adl; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ScaledPassingScore.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ScaledPassingScore.java new file mode 100644 index 00000000..e736cd0e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/ScaledPassingScore.java @@ -0,0 +1,45 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.Real7WithRange; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class ScaledPassingScore implements TerminalDataType { + + private Real7WithRange scaledPassingScore; + + public ScaledPassingScore() { + this.scaledPassingScore = new Real7WithRange(-1, 1); + registerHandler(); + } + + private void registerHandler() { + scaledPassingScore.registerGetHandler(context -> { + if (((Real7WithRange) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }).registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + this.scaledPassingScore.set(value); + return null; + } + + @Override + public ScormResult get() { + return this.scaledPassingScore.get(); + } + + public Real7WithRange getScaledPassingScore() { + return scaledPassingScore; + } + + public void setScaledPassingScore(Real7WithRange scaledPassingScore) { + this.scaledPassingScore = scaledPassingScore; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Score.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Score.java new file mode 100644 index 00000000..71de7452 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/Score.java @@ -0,0 +1,109 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.annotation.Meta; +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.GeneralDataType; +import com.xboe.module.scorm.rte.model.datatype.Real7; +import com.xboe.module.scorm.rte.model.datatype.Real7WithRange; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Score implements GeneralDataType { + + @Meta(value = "_children", writable = false) + private CharacterString children; + + @Meta("scaled") + private Real7WithRange scaled; + + @Meta("raw") + private Real7 raw; + + @Meta("max") + private Real7 max; + + @Meta("min") + private Real7 min; + + public Score() { + this.children = new CharacterString("scaled,min,max,raw"); + this.scaled = new Real7WithRange(-1, 1); + this.raw = new Real7(); + this.max = new Real7(); + this.min = new Real7(); + registerHandler(); + } + + private void registerHandler() { + children.registerSetHandler(new ReadOnlyHandler()); + + scaled.registerGetHandler(context -> { + if (((Real7WithRange) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + + raw.registerGetHandler(context -> { + if (((Real7) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + + min.registerGetHandler(context -> { + if (((Real7) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + + max.registerGetHandler(context -> { + if (((Real7) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + } + + public CharacterString getChildren() { + return children; + } + + public void setChildren(CharacterString children) { + this.children = children; + } + + public Real7WithRange getScaled() { + return scaled; + } + + public void setScaled(Real7WithRange scaled) { + this.scaled = scaled; + } + + public Real7 getRaw() { + return raw; + } + + public void setRaw(Real7 raw) { + this.raw = raw; + } + + public Real7 getMax() { + return max; + } + + public void setMax(Real7 max) { + this.max = max; + } + + public Real7 getMin() { + return min; + } + + public void setMin(Real7 min) { + this.min = min; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SessionTime.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SessionTime.java new file mode 100644 index 00000000..997b0d3a --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SessionTime.java @@ -0,0 +1,38 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.datatype.TimeInterval; +import com.xboe.module.scorm.rte.model.handler.WriteOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class SessionTime implements TerminalDataType { + + private TimeInterval sessionTime; + + public SessionTime() { + this.sessionTime = new TimeInterval(); + registerHandler(); + } + + private void registerHandler() { + sessionTime.registerGetHandler(new WriteOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return this.sessionTime.set(value); + } + + @Override + public ScormResult get() { + return this.sessionTime.get(); + } + + public TimeInterval getSessionTime() { + return sessionTime; + } + + public void setSessionTime(TimeInterval sessionTime) { + this.sessionTime = sessionTime; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SuccessStatus.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SuccessStatus.java new file mode 100644 index 00000000..c3a2e142 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SuccessStatus.java @@ -0,0 +1,67 @@ +package com.xboe.module.scorm.rte.model; + +import java.math.BigDecimal; + +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class SuccessStatus implements TerminalDataType { + + private State successStatus; + + private CMI containerCMI; + + public SuccessStatus(CMI containerCMI) { + this.successStatus = new State(new String[]{"passed", "failed", "unknown"}); + this.successStatus.setValue("unknown"); + this.containerCMI = containerCMI; + } + + @Override + public ScormResult set(String value) { + return this.successStatus.set(value); + } + + @Override + public ScormResult get() { + String evaluateValue = evaluate(); + if (evaluateValue == null) { + return successStatus.get(); + } else { + return new ScormResult(evaluateValue, ScormError.E_0); + } + } + + private String evaluate() { + BigDecimal scaledPassingScore = containerCMI.getScaledPassingScore().getScaledPassingScore().getValue(); + BigDecimal scaledScore = containerCMI.getScore().getScaled().getValue(); + String successStatus = this.successStatus.getValue(); + if (scaledPassingScore == null) { + if (successStatus == null) { + return "unknown"; + } else { + return null; + } + } else { + if (scaledScore != null) { + if (scaledScore.compareTo(scaledPassingScore) < 0) { + return "failed"; + } else { + return "passed"; + } + } else { + return "unknown"; + } + } + } + + public State getSuccessStatus() { + return successStatus; + } + + public void setSuccessStatus(State successStatus) { + this.successStatus = successStatus; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SuspendData.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SuspendData.java new file mode 100644 index 00000000..d233167b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/SuspendData.java @@ -0,0 +1,43 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.CharacterString; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class SuspendData implements TerminalDataType { + + private CharacterString suspendData; + + public SuspendData() { + this.suspendData = new CharacterString(); + registerHandler(); + } + + private void registerHandler() { + suspendData.registerGetHandler(context -> { + if (((CharacterString) context).getValue() == null) { + return new ScormResult("", ScormError.E_403); + } + return new ScormResult("", ScormError.E_0); + }); + } + + @Override + public ScormResult set(String value) { + return this.suspendData.set(value); + } + + @Override + public ScormResult get() { + return this.suspendData.get(); + } + + public CharacterString getSuspendData() { + return suspendData; + } + + public void setSuspendData(CharacterString suspendData) { + this.suspendData = suspendData; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/TimeLimitAction.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/TimeLimitAction.java new file mode 100644 index 00000000..1f4368ea --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/TimeLimitAction.java @@ -0,0 +1,40 @@ +package com.xboe.module.scorm.rte.model; + +import com.xboe.module.scorm.rte.model.datatype.State; +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class TimeLimitAction implements TerminalDataType { + + private State timeLimitAction; + + public TimeLimitAction() { + this.timeLimitAction + = new State(new String[]{"exist,message", "continue,message", "exit,no message", "continue,no message"}); + this.timeLimitAction.setValue("continue,no message"); + registerHandler(); + } + + private void registerHandler() { + timeLimitAction.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return this.timeLimitAction.set(value); + } + + @Override + public ScormResult get() { + return this.timeLimitAction.get(); + } + + public State getTimeLimitAction() { + return timeLimitAction; + } + + public void setTimeLimitAction(State timeLimitAction) { + this.timeLimitAction = timeLimitAction; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/TotalTime.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/TotalTime.java new file mode 100644 index 00000000..b3e38da2 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/TotalTime.java @@ -0,0 +1,41 @@ +package com.xboe.module.scorm.rte.model; + +import java.time.Duration; + +import com.xboe.module.scorm.rte.model.datatype.TerminalDataType; +import com.xboe.module.scorm.rte.model.datatype.TimeInterval; +import com.xboe.module.scorm.rte.model.handler.ReadOnlyHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class TotalTime implements TerminalDataType { + + private TimeInterval totalTime; + + public TotalTime() { + this.totalTime = new TimeInterval(); + this.totalTime.setValue(Duration.ZERO.toString()); + registerHandler(); + } + + private void registerHandler() { + totalTime.registerSetHandler(new ReadOnlyHandler()); + } + + @Override + public ScormResult set(String value) { + return this.totalTime.set(value); + } + + @Override + public ScormResult get() { + return this.totalTime.get(); + } + + public TimeInterval getTotalTime() { + return totalTime; + } + + public void setTotalTime(TimeInterval totalTime) { + this.totalTime = totalTime; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/annotation/Meta.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/annotation/Meta.java new file mode 100644 index 00000000..6856f84e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/annotation/Meta.java @@ -0,0 +1,20 @@ +package com.xboe.module.scorm.rte.model.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Meta { + + String value(); + + boolean readable() default true; + + boolean writable() default true; + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/AbstractCollectionDataType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/AbstractCollectionDataType.java new file mode 100644 index 00000000..a3707dbc --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/AbstractCollectionDataType.java @@ -0,0 +1,66 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import java.util.ArrayList; +import java.util.List; + +import com.xboe.module.scorm.rte.model.error.Diagnostic; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.CollectionScormResult; + +public abstract class AbstractCollectionDataType<Instance> implements CollectionDataType<Instance> { + + private List<Instance> instances; + + private Instance lastNewInstance; + + public AbstractCollectionDataType() { + instances = new ArrayList<>(); + } + + @Override + public final CollectionScormResult<Instance> get(int index) { + if (index >= instances.size()) { + return new CollectionScormResult<>("", ScormError.E_301, + Diagnostic.DATA_MODEL_COLLECTION_ELEMENT_REQUEST_OUT_OF_RANGE); + } + return new CollectionScormResult<>("", ScormError.E_0, instances.get(index)); + } + + @Override + public final CollectionScormResult<Instance> set(int index) { + Instance instance; + if (index > instances.size()) { + return new CollectionScormResult<>("false", ScormError.E_351, + Diagnostic.DATA_MODEL_ELEMENT_COLLECTION_SET_OUT_OF_ORDER); + } else if (index == instances.size()) { // need a new instance + instance = this.lastNewInstance = newInstance(); + } else { + instance = instances.get(index); + } + return new CollectionScormResult<>("true", ScormError.E_0, instance); + } + + @Override + public void syncNewInstance(boolean operatorIsSuccess) { + if (lastNewInstance == null) { + return; + } + if (operatorIsSuccess) { + instances.add(lastNewInstance); + addCount(); + } + lastNewInstance = null; + } + + protected abstract Instance newInstance(); + + protected abstract void addCount(); + + public List<Instance> getInstances() { + return instances; + } + + public void setInstances(List<Instance> instances) { + this.instances = instances; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/AbstractTerminalDataType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/AbstractTerminalDataType.java new file mode 100644 index 00000000..42a21bfb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/AbstractTerminalDataType.java @@ -0,0 +1,39 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.handler.GetHandler; +import com.xboe.module.scorm.rte.model.handler.SetHandler; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public abstract class AbstractTerminalDataType implements TerminalDataType { + + private SetHandler setHandler; + private GetHandler getHandler; + + public AbstractTerminalDataType registerSetHandler(SetHandler setHandler) { + this.setHandler = setHandler; + return this; + } + + public AbstractTerminalDataType registerGetHandler(GetHandler getHandler) { + this.getHandler = getHandler; + return this; + } + + protected ScormResult handleSet(Object context, String value) { + if (setHandler != null) { + return setHandler.handle(context, value); + } else { + return new ScormResult("true", ScormError.E_0); + } + } + + protected ScormResult handleGet(Object context) { + if (getHandler != null) { + return getHandler.handle(context); + } else { + return new ScormResult("", ScormError.E_0); + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/CharacterString.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/CharacterString.java new file mode 100644 index 00000000..417b40c2 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/CharacterString.java @@ -0,0 +1,43 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class CharacterString extends AbstractTerminalDataType { + + private String value; + + public CharacterString() { + } + + public CharacterString(String value) { + this.value = value; + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + this.value = value; + return scormResult; + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/CollectionDataType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/CollectionDataType.java new file mode 100644 index 00000000..6b922247 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/CollectionDataType.java @@ -0,0 +1,13 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import com.xboe.module.scorm.rte.model.result.CollectionScormResult; + +public interface CollectionDataType<Instance> { + + CollectionScormResult<Instance> get(int index); + + CollectionScormResult<Instance> set(int index); + + void syncNewInstance(boolean operatorIsSuccess); + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/GeneralDataType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/GeneralDataType.java new file mode 100644 index 00000000..67223434 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/GeneralDataType.java @@ -0,0 +1,5 @@ +package com.xboe.module.scorm.rte.model.datatype; + +public interface GeneralDataType { + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Int.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Int.java new file mode 100644 index 00000000..60b53766 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Int.java @@ -0,0 +1,47 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Int extends AbstractTerminalDataType { + + private int value; + + public Int() { + } + + public Int(int value) { + this.value = value; + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + try { + this.value = Integer.parseInt(value); + return scormResult; + } catch (Exception e) { + return new ScormResult("false", ScormError.E_406, "parameter should be a integer"); + } + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(String.valueOf(value)); + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/LocalizedString.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/LocalizedString.java new file mode 100644 index 00000000..dcec1117 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/LocalizedString.java @@ -0,0 +1,86 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; +import com.xboe.module.scorm.rte.model.util.ModelUtils; + +public class LocalizedString extends AbstractTerminalDataType { + + private String language; + private String content; + private String value; + + public LocalizedString() { + } + + public LocalizedString(String value) { + set0(value); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + if (!set0(value)) { + return new ScormResult("false", ScormError.E_406); + } + return scormResult; + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value); + } + + private boolean set0(String value) { + int startIndex = value.indexOf("{"); + int endIndex = value.indexOf("}"); + if (startIndex != 0 || endIndex < 0) { + defaultSet(value); + return true; + } + String descriptor = value.substring(0, endIndex + 1); + if (!ModelUtils.isDelimiterFormatCorrect(descriptor, "lang")) { + defaultSet(value); + return true; + } + String dv = descriptor.substring(1, descriptor.length() - 1).split("=")[1]; + if (CommonUtils.isLegalLanguage(dv)) { + this.language = dv; + this.content = value.substring(endIndex + 1); + this.value = value; + return true; + } + return false; + } + + private void defaultSet(String value) { + this.language = "en"; + this.content = value; + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + set0(value); + } + + public String getLanguage() { + return language; + } + + public String getContent() { + return content; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/LongIdentifier.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/LongIdentifier.java new file mode 100644 index 00000000..47f6c745 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/LongIdentifier.java @@ -0,0 +1,61 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import org.apache.commons.lang3.StringUtils; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class LongIdentifier extends AbstractTerminalDataType { + + private String value; + + public LongIdentifier() { + + } + + public LongIdentifier(String value) { + set0(value); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + if (!set0(value)) { + return new ScormResult("false", ScormError.E_406); + } + return scormResult; + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value); + } + + private boolean set0(String value) { + if (StringUtils.isBlank(value)) { + return false; + } + if (CommonUtils.isLegalURI(value)) { + this.value = value; + return true; + } else { + return false; + } + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + set0(value); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/MapDataType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/MapDataType.java new file mode 100644 index 00000000..5a34fbb6 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/MapDataType.java @@ -0,0 +1,11 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public interface MapDataType { + + ScormResult set(String key, String value); + + ScormResult get(String key); + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7.java new file mode 100644 index 00000000..1d9ec484 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7.java @@ -0,0 +1,54 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import java.math.BigDecimal; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Real7 extends AbstractTerminalDataType { + + private BigDecimal value; + + public Real7() { + } + + public Real7(double value) { + this(String.valueOf(value)); + } + + public Real7(String value) { + set(value); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + try { + this.value = BigDecimal.valueOf(Double.parseDouble(value)) + .setScale(7, BigDecimal.ROUND_HALF_UP); + return scormResult; + } catch (Exception e) { + return new ScormResult("false", ScormError.E_406, "parameter should be a decimal"); + } + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value.toString()); + } + + public BigDecimal getValue() { + return value; + } + + public void setValue(BigDecimal value) { + this.value = value; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7WithMin.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7WithMin.java new file mode 100644 index 00000000..8fe7e9b1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7WithMin.java @@ -0,0 +1,62 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import java.math.BigDecimal; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Real7WithMin extends AbstractTerminalDataType { + + private BigDecimal value; + private BigDecimal min; + + public Real7WithMin(double min) { + this.min = new BigDecimal(min).setScale(7, BigDecimal.ROUND_HALF_UP); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + try { + BigDecimal num = new BigDecimal(value).setScale(7, BigDecimal.ROUND_HALF_UP); + if (min.compareTo(num) <= 0) { + this.value = num; + return scormResult; + } else { + return new ScormResult("false", ScormError.E_407, + "parameter should be a decimal, with range [" + min.toString() + ", ...)"); + } + } catch (Exception e) { + return new ScormResult("false", ScormError.E_406, + "parameter should be a decimal, with range [" + min.toString() + ", ...)"); + } + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value.toString()); + } + + public BigDecimal getValue() { + return value; + } + + public void setValue(BigDecimal value) { + this.value = value; + } + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7WithRange.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7WithRange.java new file mode 100644 index 00000000..9b0ae33f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Real7WithRange.java @@ -0,0 +1,73 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import java.math.BigDecimal; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Real7WithRange extends AbstractTerminalDataType { + + private BigDecimal value; + private BigDecimal min; + private BigDecimal max; + + public Real7WithRange(double min, double max) { + this.min = new BigDecimal(min).setScale(7, BigDecimal.ROUND_HALF_UP); + this.max = new BigDecimal(max).setScale(7, BigDecimal.ROUND_HALF_UP); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + try { + BigDecimal num = new BigDecimal(value).setScale(7, BigDecimal.ROUND_HALF_UP); + if (min.compareTo(num) <= 0 && num.compareTo(max) <= 0) { + this.value = num; + return scormResult; + } else { + return new ScormResult("false", ScormError.E_407, + "parameter should be a decimal, with range [" + min.toString() + ", " + max.toString() + "]"); + } + } catch (Exception e) { + return new ScormResult("false", ScormError.E_406, + "parameter should be a decimal, with range [" + min.toString() + ", " + max.toString() + "]"); + + } + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value.toString()); + } + + public BigDecimal getValue() { + return value; + } + + public void setValue(BigDecimal value) { + this.value = value; + } + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/State.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/State.java new file mode 100644 index 00000000..7a92e2d1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/State.java @@ -0,0 +1,64 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class State extends AbstractTerminalDataType { + + private String value; + private Set<String> stateTable; + + public State(String value, Set<String> stateTable) { + this.stateTable = stateTable; + set(value); + } + + public State(String[] stateTable) { + this.stateTable = new HashSet<>(Arrays.asList(stateTable)); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + if (stateTable.contains(value)) { + this.value = value; + return scormResult; + } else { + return new ScormResult("false", ScormError.E_406, CommonUtils.format( + "parameter should be one of the following tokens: {}", (Object) stateTable.toArray())); + } + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Set<String> getStateTable() { + return stateTable; + } + + public void setStateTable(Set<String> stateTable) { + this.stateTable = stateTable; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/StateReadOnlyMap.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/StateReadOnlyMap.java new file mode 100644 index 00000000..bc593db5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/StateReadOnlyMap.java @@ -0,0 +1,50 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class StateReadOnlyMap implements MapDataType { + + private final Set<String> stateTable; + + private final Map<String, String> map; + + public StateReadOnlyMap(String... states) { + this.stateTable = new HashSet<>(Arrays.asList(states)); + this.map = new ConcurrentHashMap<>(); + } + + @Override + public ScormResult set(String key, String value) { + return new ScormResult("false", ScormError.E_404); + } + + @Override + public ScormResult get(String key) { + String value = map.get(key); + if (key == null || value == null) { + return new ScormResult("", ScormError.E_301); + } + return new ScormResult(value, ScormError.E_0); + } + + public void put(String key, String value) { + map.put(key, value); + } + + public void remove(String key) { + map.remove(key); + } + + public String getValue(String key) { + return map.get(key); + } + + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/TerminalDataType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/TerminalDataType.java new file mode 100644 index 00000000..057196b7 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/TerminalDataType.java @@ -0,0 +1,11 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public interface TerminalDataType { + + ScormResult set(String value); + + ScormResult get(); + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Time.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Time.java new file mode 100644 index 00000000..590a2013 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/Time.java @@ -0,0 +1,60 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import org.apache.commons.lang3.StringUtils; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class Time extends AbstractTerminalDataType { + + private String value; + + public Time() { + } + + public Time(String value) { + set0(value); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + if (!set0(value)) { + return new ScormResult("false", ScormError.E_406); + } + return scormResult; + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value); + } + + private boolean set0(String value) { + if (StringUtils.isBlank(value)) { + return false; + } + if (CommonUtils.isLegalTime(value)) { + this.value = value; + return true; + } else { + return false; + } + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + set0(value); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/TimeInterval.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/TimeInterval.java new file mode 100644 index 00000000..58dce951 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/datatype/TimeInterval.java @@ -0,0 +1,60 @@ +package com.xboe.module.scorm.rte.model.datatype; + +import org.apache.commons.lang3.StringUtils; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class TimeInterval extends AbstractTerminalDataType { + + private String value; + + public TimeInterval() { + } + + public TimeInterval(String value) { + set0(value); + } + + @Override + public ScormResult set(String value) { + ScormResult scormResult = super.handleSet(this, value); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + if (!set0(value)) { + return new ScormResult("false", ScormError.E_406); + } + return scormResult; + } + + @Override + public ScormResult get() { + ScormResult scormResult = super.handleGet(this); + if (!scormResult.getError().equals(ScormError.E_0)) { + return scormResult; + } + return scormResult.setReturnValue(value); + } + + private boolean set0(String value) { + if (StringUtils.isBlank(value)) { + return false; + } + if (CommonUtils.isLegalDuration(value)) { + this.value = value; + return true; + } else { + return false; + } + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/error/Diagnostic.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/error/Diagnostic.java new file mode 100644 index 00000000..9f6725f5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/error/Diagnostic.java @@ -0,0 +1,32 @@ +package com.xboe.module.scorm.rte.model.error; + +public final class Diagnostic { + + public static final String DATA_MODEL_ELEMENT_DOSE_NOT_HAVE_CHILDREN + = "Data Model Element Does Not Have Children"; + + public static final String DATA_MODEL_ELEMENT_CANNOT_HAVE_COUNT + = "Data Model Element Cannot Have Count"; + + public static final String DATA_MODEL_ELEMENT_COLLECTION_SET_OUT_OF_ORDER + = "Data Model Element Collection Set Out Of Order"; + + public static final String DATA_MODEL_COLLECTION_ELEMENT_REQUEST_OUT_OF_RANGE + = "Data Model Collection Element Request Out Of Range"; + + public static final String DATA_MODEL_ELEMENT_NOT_SPECIFIED + = "Data Model Element Not Specified"; + + public static final String UNIQUE_IDENTIFIER_CONSTRAINT_VIOLATED + = "Unique Identifier Constraint Violated"; + + public static final String SMALLEST_PERMITTED_MAXIMUM_EXCEEDED + = "Smallest Permitted Maximum Exceeded"; + + public static final String DATA_MODEL_ELEMENT_DOSE_NOT_HAVE_VERSION + = "Data Model Element Dost Not Have Version"; + + public static final String IDENTIFIER_VALUE_CAN_ONLY_BE_SET_ONCE + = "Identifier Value Can Only Be Set Once"; + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/error/ScormError.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/error/ScormError.java new file mode 100644 index 00000000..bad61515 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/error/ScormError.java @@ -0,0 +1,61 @@ +package com.xboe.module.scorm.rte.model.error; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +public enum ScormError { + E_0(0, "No Error"), + E_101(101, "General Exception"), + E_102(102, "General Initialization Failure"), + E_103(103, "Already Initialized"), + E_104(105, "Content Instance Terminated"), + E_111(111, "General Termination Failure"), + E_112(112, "Termination Before Initialization"), + E_113(113, "Termination After Termination"), + E_122(122, "Retrieve Data Before Initialization"), + E_123(123, "Retrieve Data After Termination"), + E_132(132, "Store Data Before Initialization"), + E_133(133, "Store Data After Termination"), + E_142(142, "Commit Before Initialization"), + E_143(143, "Commit After Termination"), + E_201(201, "General Argument Error"), + E_301(301, "General Get Failure"), + E_351(351, "General Set Failure"), + E_391(391, "General Commit Failure"), + E_401(401, "Undefined Data Model Element"), + E_402(402, "Unimplemented Data Model Element"), + E_403(403, "Data Model Element Value Not Initialized"), + E_404(404, "DataModel Element Is Read Only"), + E_405(405, "Data Model Element Is Write Only"), + E_406(406, "Data Model Element Type Mismatch"), + E_407(407, "Data Model Element Value Out Of Range"), + E_408(408, "Data Model Dependency Not Established"); + + private int code; + + private String msg; + + ScormError(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int getCode() { + return code; + } + + public String getMsg() { + return msg; + } + + public static ScormError getByCode(int c) { + return ScormError.valueOf("E_" + c); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("code", code) + .append("msg", msg) + .toString(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/GetHandler.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/GetHandler.java new file mode 100644 index 00000000..d8a18ec5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/GetHandler.java @@ -0,0 +1,9 @@ +package com.xboe.module.scorm.rte.model.handler; + +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public interface GetHandler { + + ScormResult handle(Object context); + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/ReadOnlyHandler.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/ReadOnlyHandler.java new file mode 100644 index 00000000..00cd68a6 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/ReadOnlyHandler.java @@ -0,0 +1,12 @@ +package com.xboe.module.scorm.rte.model.handler; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class ReadOnlyHandler implements SetHandler { + + @Override + public ScormResult handle(Object context, String value) { + return new ScormResult("false", ScormError.E_404); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/SetHandler.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/SetHandler.java new file mode 100644 index 00000000..0d97632e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/SetHandler.java @@ -0,0 +1,9 @@ +package com.xboe.module.scorm.rte.model.handler; + +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public interface SetHandler { + + ScormResult handle(Object context, String value); + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/WriteOnlyHandler.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/WriteOnlyHandler.java new file mode 100644 index 00000000..63cff7eb --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/handler/WriteOnlyHandler.java @@ -0,0 +1,12 @@ +package com.xboe.module.scorm.rte.model.handler; + +import com.xboe.module.scorm.rte.model.error.ScormError; +import com.xboe.module.scorm.rte.model.result.ScormResult; + +public class WriteOnlyHandler implements GetHandler { + + @Override + public ScormResult handle(Object context) { + return new ScormResult("", ScormError.E_405); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/result/CollectionScormResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/result/CollectionScormResult.java new file mode 100644 index 00000000..07eafeaa --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/result/CollectionScormResult.java @@ -0,0 +1,37 @@ +package com.xboe.module.scorm.rte.model.result; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.rte.model.error.ScormError; + +public class CollectionScormResult<Instance> extends ScormResult { + + private Instance instance; + + public CollectionScormResult(@NotNull String returnValue, @NotNull ScormError error) { + this(returnValue, error, (Instance) null); + } + + public CollectionScormResult(@NotNull String returnValue, @NotNull ScormError error, @NotNull String diagnostic) { + this(returnValue, error, diagnostic, (Instance) null); + } + + public CollectionScormResult(@NotNull String returnValue, @NotNull ScormError error, Instance instance) { + super(returnValue, error); + this.instance = instance; + } + + public CollectionScormResult(@NotNull String returnValue, @NotNull ScormError error, + @NotNull String diagnostic, Instance instance) { + super(returnValue, error, diagnostic); + this.instance = instance; + } + + public Instance getInstance() { + return instance; + } + + public void setInstance(Instance instance) { + this.instance = instance; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/result/ScormResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/result/ScormResult.java new file mode 100644 index 00000000..4b61f856 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/result/ScormResult.java @@ -0,0 +1,57 @@ +package com.xboe.module.scorm.rte.model.result; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.rte.model.error.ScormError; + +public class ScormResult { + + private String returnValue; + + private ScormError error; + + private String diagnostic; + + public ScormResult(@NotNull String returnValue, @NotNull ScormError error) { + this(returnValue, error, error.getMsg()); + } + + public ScormResult(@NotNull String returnValue, @NotNull ScormError error, @NotNull String diagnostic) { + this.returnValue = returnValue; + this.error = error; + this.diagnostic = diagnostic; + } + + public String getReturnValue() { + return returnValue; + } + + public ScormResult setReturnValue(@NotNull String returnValue) { + this.returnValue = returnValue; + return this; + } + + public ScormError getError() { + return error; + } + + public String getDiagnostic() { + return diagnostic; + } + + public ScormResult setDiagnostic(@NotNull String diagnostic) { + this.diagnostic = diagnostic; + return this; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("returnValue", returnValue) + .append("error", error) + .append("diagnostic", diagnostic) + .toString(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/util/ModelUtils.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/util/ModelUtils.java new file mode 100644 index 00000000..6d75fec2 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/rte/model/util/ModelUtils.java @@ -0,0 +1,19 @@ +package com.xboe.module.scorm.rte.model.util; + +public class ModelUtils { + + public static boolean isDelimiterFormatCorrect(String delimiter, String name) { + if (!delimiter.startsWith("{") || !delimiter.endsWith("}")) { + return false; + } + String[] content = delimiter.substring(1, delimiter.length() - 1).split("="); + if (content.length != 2) { + return false; + } + if (!content[1].equals(name)) { + return false; + } + return true; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ActivityAttempt.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ActivityAttempt.java new file mode 100644 index 00000000..82d5d801 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ActivityAttempt.java @@ -0,0 +1,94 @@ +package com.xboe.module.scorm.sn.api; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.sn.model.tree.Activity; + +public final class ActivityAttempt { + + private final ID id; + + private final Activity targetActivity; + + private final ActivityAttempt parentAttempt; + + private final List<ActivityAttempt> childrenAttempt; + + private final boolean isRoot; + + private boolean state; + + public ActivityAttempt(Activity targetActivity, ActivityAttempt parentAttempt) { + this.targetActivity = targetActivity; + this.parentAttempt = parentAttempt; + this.childrenAttempt = new ArrayList<>(); + this.id = targetActivity.getId(); + this.isRoot = parentAttempt == null; + if (parentAttempt != null) { + parentAttempt.childrenAttempt.add(this); + } + active(); + } + + public final ID getId() { + return id; + } + + public Activity getTargetActivity() { + return targetActivity; + } + + public ActivityAttempt getParentAttempt() { + return parentAttempt; + } + + public List<ActivityAttempt> getChildrenAttempt() { + return childrenAttempt; + } + + public boolean isRoot() { + return isRoot; + } + + public void active() { + this.state = true; + } + + public void suspend() { + this.state = false; + } + + public boolean isActive() { + return state; + } + + public boolean isSuspend() { + return !state; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ActivityAttempt that = (ActivityAttempt) o; + + return new EqualsBuilder() + .append(id, that.id) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(id) + .toHashCode(); + } +} + diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ActivityTreeGenerator.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ActivityTreeGenerator.java new file mode 100644 index 00000000..c9bd190b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ActivityTreeGenerator.java @@ -0,0 +1,626 @@ +package com.xboe.module.scorm.sn.api; + +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import lombok.extern.slf4j.Slf4j; + +import com.xboe.module.scorm.cam.model.AdlseqMapInfo; +import com.xboe.module.scorm.cam.model.AdlseqObjective; +import com.xboe.module.scorm.cam.model.CompletionThreshold; +import com.xboe.module.scorm.cam.model.ConditionRule; +import com.xboe.module.scorm.cam.model.ConstrainedChoiceConsiderations; +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.cam.model.ControlMode; +import com.xboe.module.scorm.cam.model.HideLMSUI; +import com.xboe.module.scorm.cam.model.Item; +import com.xboe.module.scorm.cam.model.MapInfo; +import com.xboe.module.scorm.cam.model.NavigationInterface; +import com.xboe.module.scorm.cam.model.Objective; +import com.xboe.module.scorm.cam.model.Objectives; +import com.xboe.module.scorm.cam.model.Organization; +import com.xboe.module.scorm.cam.model.Presentation; +import com.xboe.module.scorm.cam.model.RandomizationControls; +import com.xboe.module.scorm.cam.model.RollupConsiderations; +import com.xboe.module.scorm.cam.model.RollupRule; +import com.xboe.module.scorm.cam.model.RollupRules; +import com.xboe.module.scorm.cam.model.Sequencing; +import com.xboe.module.scorm.cam.model.SequencingCollection; +import com.xboe.module.scorm.cam.model.SequencingRules; +import com.xboe.module.scorm.cam.model.util.CPUtils; +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.common.LMSPersistDriverManager; +import com.xboe.module.scorm.rte.api.LMSLearnerInfo; +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; +import com.xboe.module.scorm.sn.model.definition.ConstrainChoiceControls; +import com.xboe.module.scorm.sn.model.definition.LimitConditions; +import com.xboe.module.scorm.sn.model.definition.ObjectiveDescription; +import com.xboe.module.scorm.sn.model.definition.ObjectiveMap; +import com.xboe.module.scorm.sn.model.definition.RollupCondition; +import com.xboe.module.scorm.sn.model.definition.RollupConsiderationControls; +import com.xboe.module.scorm.sn.model.definition.RollupRuleDescription; +import com.xboe.module.scorm.sn.model.definition.RuleCondition; +import com.xboe.module.scorm.sn.model.definition.SequencingControlMode; +import com.xboe.module.scorm.sn.model.definition.SequencingDefinition; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription.ConditionType; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +@Slf4j +public class ActivityTreeGenerator { + + private static LMSPersistDriverManager lmsPersistDriverManager = LMSPersistDriverManager.getInstance(); + + public static ActivityTree deriveActivityTreesFrom(ContentPackage contentPackage, String lmsContentPackageID, + LMSLearnerInfo lmsLearnerInfo, String organizationId) { + SequencingCollection sequencingCollection = contentPackage.getManifest().getSequencingCollection(); + for (Organization organization : contentPackage.getManifest().getOrganizations().getOrganizationList()) { + if (StringUtils.equals(organization.getIdentifier().getValue(), organizationId)) { + ID id = new ID(organization.getIdentifier().getValue(), lmsContentPackageID, lmsLearnerInfo.getLearnerID()); + ActivityTree activityTree = deriveActivityTreeFrom(organization, sequencingCollection, id); + if (activityTree == null) { + log.error("derive activity tree error - activity tree \"{}\"", id); + break; + } else { + initAvailableChildren(activityTree); + return activityTree; + } + } + } + return null; + } + + private static ActivityTree deriveActivityTreeFrom( + Organization organization, SequencingCollection sequencingCollection, ID id) { + ActivityTree activityTree = new ActivityTree(id); + activityTree.setObjectivesGlobalToSystem(organization.isObjectivesGlobalToSystem()); + // root + Activity root = new Activity( + new ID(organization.getIdentifier().getValue(), id.getLmsContentPackageID(), id.getLmsLearnerID()), activityTree); + root.setTitle(organization.getTitle()); + initSequencingDefinition(root, organization.getSequencing(), sequencingCollection); + initCompletionThreshold(root, organization.getCompletionThreshold()); + activityTree.setRoot(root); + for (Item item : organization.getItemList()) { + Activity activity = deriveActivityFrom(activityTree, item, sequencingCollection, + new ID(item.getIdentifier().getValue(), id.getLmsContentPackageID(), id.getLmsLearnerID()), root); + root.getChildren().add(activity); + } + initActivityProgressInformation(root); + return activityTree; + } + + private static Activity deriveActivityFrom(ActivityTree activityTree, + Item item, SequencingCollection sequencingCollection, ID id, Activity parentActivity) { + Activity activity = new Activity(id, activityTree); + activity.setParentActivity(parentActivity); + activity.setVisible(item.isIsvisible()); + activity.setTitle(item.getTitle()); + if (item.getParameters() != null) { + activity.setParameters(item.getParameters()); + } + if (item.getIdentifierref() != null) { + activity.setReferenceResource(item.getIdentifierref()); + } + initSequencingDefinition(activity, item.getSequencing(), sequencingCollection); + initCompletionThreshold(activity, item.getCompletionThreshold()); + initPresentation(activity, item.getPresentation()); + for (Item childItem : item.getItemList()) { + Activity childActivity = deriveActivityFrom(activityTree, item, sequencingCollection, + new ID(childItem.getIdentifier().getValue(), id.getLmsContentPackageID(), id.getLmsLearnerID()), activity); + activity.getChildren().add(childActivity); + } + initActivityProgressInformation(activity); + return activity; + } + + private static void initAvailableChildren(ActivityTree activityTree) { + initAvailableChildren(activityTree.getRoot()); + } + + private static void initAvailableChildren(Activity activity) { + if (activity == null) { + return; + } + for (Activity child : activity.getChildren()) { + activity.getActivityStateInformation().getAvailableChildren().add(child); + initAvailableChildren(child); + } + } + + private static void initSequencingDefinition(Activity activity, Sequencing sequencing, SequencingCollection sequencingCollection) { + if (sequencing != null) { + if (sequencing.getIdRef() != null && StringUtils.isNotBlank(sequencing.getIdRef().getValue()) + && sequencingCollection != null) { + initSequencingDefinition( + activity, CPUtils.findSequencingByID(sequencingCollection, sequencing.getIdRef().getValue())); + } + initSequencingDefinition(activity, sequencing); + } + } + + private static void initActivityProgressInformation(Activity activity) { + if (lmsPersistDriverManager.getDriver() == null) { + log.error("not found lms persist driver"); + return; + } + ID id = activity.getId(); + int attemptCount = lmsPersistDriverManager.getDriver().queryActivityAttemptCountBy( + id.getLmsContentPackageID(), id.getIdentifier(), id.getLmsLearnerID()); + if (attemptCount > 0) { + activity.getActivityProgressInformation().setActivityProgressStatus(true) + .getActivityAttemptCount().setValue(attemptCount); + } + } + + private static void initSequencingDefinition(Activity activity, Sequencing sequencing) { + if (sequencing == null) { + return; + } + SequencingDefinition sequencingDefinition = activity.getSequencingDefinition(); + // SequencingControlMode + if (sequencing.getControlMode() != null) { + ControlMode controlMode = sequencing.getControlMode(); + SequencingControlMode sequencingControlMode = sequencingDefinition.getSequencingControlMode(); + sequencingControlMode.setSequencingControlChoice(controlMode.isChoice()); + sequencingControlMode.setSequencingControlChoiceExit(controlMode.isChoiceExit()); + sequencingControlMode.setSequencingControlFlow(controlMode.isFlow()); + sequencingControlMode.setSequencingControlForwardOnly(controlMode.isForwardOnly()); + sequencingControlMode.setUseCurrentAttemptObjectiveInformation(controlMode.isUseCurrentAttemptObjectiveInfo()); + sequencingControlMode.setUseCurrentAttemptProgressInformation(controlMode.isUseCurrentAttemptProgressInfo()); + } + // ConstrainChoiceControls + if (sequencing.getConstrainedChoiceConsiderations() != null) { + ConstrainedChoiceConsiderations constrainedChoiceConsiderations = sequencing.getConstrainedChoiceConsiderations(); + ConstrainChoiceControls constrainChoiceControls = sequencingDefinition.getConstrainChoiceControls(); + constrainChoiceControls.setConstrainChoice(constrainedChoiceConsiderations.isConstrainChoice()); + constrainChoiceControls.setPreventActivation(constrainedChoiceConsiderations.isPreventActivation()); + } + // SequencingRuleDescriptions + if (sequencing.getSequencingRules() != null) { + SequencingRules sequencingRules = sequencing.getSequencingRules(); + List<SequencingRuleDescription> sequencingRuleDescriptions = sequencingDefinition.getSequencingRuleDescriptions(); + for (ConditionRule conditionRule : sequencingRules.getPreConditionRuleList()) { + sequencingRuleDescriptions.add(deriveSequencingRuleDescriptionFrom(conditionRule, ConditionType.PRECONDITION)); + } + for (ConditionRule conditionRule : sequencingRules.getPostConditionRuleList()) { + sequencingRuleDescriptions.add(deriveSequencingRuleDescriptionFrom(conditionRule, ConditionType.POSTCONDITION)); + } + for (ConditionRule conditionRule : sequencingRules.getExitConditionRuleList()) { + sequencingRuleDescriptions.add(deriveSequencingRuleDescriptionFrom(conditionRule, ConditionType.EXITCONDITION)); + } + } + // LimitConditions + if (sequencing.getLimitConditions() != null) { + com.xboe.module.scorm.cam.model.LimitConditions limit = sequencing.getLimitConditions(); + LimitConditions limitConditions = sequencingDefinition.getLimitConditions(); + // attemptControl & attemptLimit + if (limit.getAttemptLimit() != null) { + limitConditions.setAttemptControl(true); + limitConditions.getAttemptLimit().setValue(limit.getAttemptLimit().getIntValue()); + } else { + limitConditions.setAttemptControl(false); + } + // attemptAbsoluteDurationControl & attemptAbsoluteDurationLimit + if (StringUtils.isNotBlank(limit.getAttemptAbsoluteDurationLimit())) { + limitConditions.setAttemptAbsoluteDurationControl(true); + limitConditions.setAttemptAbsoluteDurationLimit(Duration.parse(limit.getAttemptAbsoluteDurationLimit())); + } else { + limitConditions.setAttemptAbsoluteDurationControl(false); + } + } + // RollupRuleDescriptions & RollupControls + if (sequencing.getRollupRules() != null) { + RollupRules rollupRules = sequencing.getRollupRules(); + for (RollupRule rollupRule : rollupRules.getRollupRuleList()) { + RollupRuleDescription description = new RollupRuleDescription(); + // conditionCombination + switch (rollupRule.getRollupConditions().getConditionCombination().getValue()) { + case "all": + description.getConditionCombination().setValue("All"); + break; + case "any": + description.getConditionCombination().setValue("Any"); + break; + } + // childActivitySet + switch (rollupRule.getChildActivitySet().getValue()) { + case "all": + description.getChildActivitySet().setValue("All"); + break; + case "any": + description.getChildActivitySet().setValue("Any"); + break; + case "none": + description.getChildActivitySet().setValue("None"); + break; + case "atLeastCount": + description.getChildActivitySet().setValue("At Least Count"); + break; + case "atLeastPercent": + description.getChildActivitySet().setValue("At Least Percent"); + break; + } + // rollupMinimumCount + description.getRollupMinimumCount().setValue(rollupRule.getMinimumCount().getIntValue()); + // rollupMinimumPercent + description.getRollupMinimumPercent().setValue(rollupRule.getMinimumPercent().getDecimalValue().doubleValue()); + // rollupAction + switch (rollupRule.getRollupAction().getValue()) { + case "satisfied": + description.getRollupAction().setValue("Satisfied"); + break; + case "notSatisfied": + description.getRollupAction().setValue("Not Satisfied"); + break; + case "completed": + description.getRollupAction().setValue("Completed"); + break; + case "incomplete": + description.getRollupAction().setValue("Incomplete"); + break; + } + // rollupConditions + for (com.xboe.module.scorm.cam.model.RollupCondition condition : + rollupRule.getRollupConditions().getRollupConditionList()) { + RollupCondition rollupCondition = new RollupCondition(); + switch (condition.getCondition().getValue()) { + case "satisfied": + rollupCondition.getRollupCondition().setValue("Satisfied"); + break; + case "objectiveStatusKnown": + rollupCondition.getRollupCondition().setValue("Objective Status Known"); + break; + case "objectiveMeasureKnown": + rollupCondition.getRollupCondition().setValue("Objective Measure Known"); + break; + case "completed": + rollupCondition.getRollupCondition().setValue("Completed"); + break; + case "activityProgressKnown": + rollupCondition.getRollupCondition().setValue("Activity Progress Known"); + break; + case "attempted": + rollupCondition.getRollupCondition().setValue("Attempted"); + break; + case "attemptLimitExceeded": + rollupCondition.getRollupCondition().setValue("Attempt Limit Exceeded"); + break; + case "timeLimitExceeded": + rollupCondition.getRollupCondition().setValue("Never"); + break; + case "outsideAvailableTimeRange": + rollupCondition.getRollupCondition().setValue("Never"); + break; + } + switch (condition.getOperator().getValue()) { + case "not": + rollupCondition.getOperator().setValue("Not"); + break; + case "noOp": + rollupCondition.getOperator().setValue("NO-OP"); + break; + } + description.getRollupConditions().add(rollupCondition); + } + sequencingDefinition.getRollupRuleDescriptions().add(description); + } + // rollupObjectiveSatisfied + sequencingDefinition.getRollupControls().setRollupObjectiveSatisfied(rollupRules.isRollupObjectiveSatisfied()); + // rollupObjectiveMeasureWeight + sequencingDefinition.getRollupControls().getRollupObjectiveMeasureWeight().setValue( + rollupRules.getObjectiveMeasureWeight().getDecimalValue().doubleValue()); + // rollupProgressCompletion + sequencingDefinition.getRollupControls().setRollupProgressCompletion(rollupRules.isRollupProgressCompletion()); + } + // RollupConsiderationControls + if (sequencing.getRollupConsiderations() != null) { + RollupConsiderationControls rollupConsiderationControls = sequencingDefinition.getRollupConsiderationControls(); + RollupConsiderations rollupConsiderations = sequencing.getRollupConsiderations(); + rollupConsiderationControls.getRequiredForSatisfied() + .setValue(rollupConsiderations.getRequiredForSatisfied().getValue()); + rollupConsiderationControls.getRequiredForNotSatisfied() + .setValue(rollupConsiderations.getRequiredForNotSatisfied().getValue()); + rollupConsiderationControls.getRequiredForCompleted() + .setValue(rollupConsiderations.getRequiredForCompleted().getValue()); + rollupConsiderationControls.getRequiredForIncomplete() + .setValue(rollupConsiderations.getRequiredForIncomplete().getValue()); + rollupConsiderationControls.setMeasureSatisfactionIfActive(rollupConsiderations.isMeasureSatisfactionIfActive()); + } + // objectiveDescriptions + if (sequencing.getObjectives() != null) { + Objectives objectives = sequencing.getObjectives(); + sequencingDefinition.getObjectiveDescriptions().add(deriveObjectiveDescriptionFrom(activity, + objectives.getPrimaryObjective(), + CPUtils.findAdlseqObjectiveByID(sequencing.getAdlseqObjectives(), + objectives.getPrimaryObjective().getObjectiveID()), true)); + for (Objective objective : objectives.getObjectiveList()) { + sequencingDefinition.getObjectiveDescriptions().add(deriveObjectiveDescriptionFrom(activity, objective, + CPUtils.findAdlseqObjectiveByID(sequencing.getAdlseqObjectives(), objective.getObjectiveID()), + false)); + } + } + // SelectionControls & RandomizationControls + if (sequencing.getRandomizationControls() != null) { + RandomizationControls randomizationControls = sequencing.getRandomizationControls(); + setTiming(randomizationControls.getSelectionTiming().getValue(), + sequencingDefinition.getSelectionControls().getSelectionTiming()); + if (randomizationControls.getSelectCount() != null) { + sequencingDefinition.getSelectionControls().setSelectionCountStatus(true); + sequencingDefinition.getSelectionControls().getSelectionCount().setValue( + randomizationControls.getSelectCount().getIntValue()); + } else { + sequencingDefinition.getSelectionControls().setSelectionCountStatus(false); + } + setTiming(randomizationControls.getRandomizationTiming().getValue(), + sequencingDefinition.getRandomizationControls().getRandomizationTiming()); + sequencingDefinition.getRandomizationControls().setRandomizeChildren(randomizationControls.isReorderChildren()); + } + // DeliveryControls + if (sequencing.getDeliveryControls() != null) { + sequencingDefinition.getDeliveryControls().setTracked(sequencing.getDeliveryControls().isTracked()); + sequencingDefinition.getDeliveryControls().setCompletionSetByContent(sequencing.getDeliveryControls().isCompletionSetByContent()); + sequencingDefinition.getDeliveryControls().setObjectiveSetByContent(sequencing.getDeliveryControls().isObjectiveSetByContent()); + } + } + + private static void setTiming(String rawValue, Vocabulary filed) { + switch (rawValue) { + case "never": + filed.setValue("Never"); + break; + case "once": + filed.setValue("Once"); + break; + case "onEachNewAttempt": + filed.setValue("On Each New Attempt"); + break; + } + } + + private static ObjectiveDescription deriveObjectiveDescriptionFrom(Activity activity, + Objective objective, AdlseqObjective adlseqObjective, boolean isPrimary) { + String objectiveID = objective.getObjectiveID() != null + && StringUtils.isNotBlank(objective.getObjectiveID().getValue()) ? objective.getObjectiveID().getValue() : null; + ObjectiveDescription description = new ObjectiveDescription(isPrimary, activity); + if (objectiveID != null) { + description.setObjectiveID(objectiveID); + } + description.setObjectiveSatisfiedByMeasure(objective.isSatisfiedByMeasure()); + description.getObjectiveMinimumSatisfiedNormalizedMeasure().setValue( + objective.getMinNormalizedMeasure().getDecimalValue().doubleValue()); + Set<String> processedID = new HashSet<>(); + for (MapInfo mapInfo : objective.getMapInfoList()) { + String targetObjectiveID = mapInfo.getTargetObjectiveID() != null + && StringUtils.isNotBlank(mapInfo.getTargetObjectiveID().getValue()) ? + mapInfo.getTargetObjectiveID().getValue() : null; + AdlseqMapInfo adlseqMapInfo = CPUtils.findAdlseqMapInfoByID(adlseqObjective, mapInfo.getTargetObjectiveID()); + ObjectiveMap objectiveMap = deriveObjectiveMapFrom(mapInfo, adlseqMapInfo, objectiveID, targetObjectiveID); + if (objectiveMap != null) { + processedID.add(targetObjectiveID); + description.getObjectiveMaps().add(objectiveMap); + } + } + if (adlseqObjective != null) { + for (AdlseqMapInfo adlseqMapInfo : adlseqObjective.getMapInfoList()) { + String targetObjectiveID = adlseqMapInfo.getTargetObjectiveID() != null + && StringUtils.isNotBlank(adlseqMapInfo.getTargetObjectiveID().getValue()) ? + adlseqMapInfo.getTargetObjectiveID().getValue() : null; + if (targetObjectiveID == null || processedID.contains(targetObjectiveID)) { + continue; + } + MapInfo mapInfo = CPUtils.findMapInfoByID(objective, adlseqMapInfo.getTargetObjectiveID()); + ObjectiveMap objectiveMap = deriveObjectiveMapFrom(mapInfo, adlseqMapInfo, objectiveID, targetObjectiveID); + if (objectiveMap != null) { + description.getObjectiveMaps().add(objectiveMap); + } + } + } + return description; + } + + private static ObjectiveMap deriveObjectiveMapFrom( + MapInfo mapInfo, AdlseqMapInfo adlseqMapInfo, String objectiveID, String targetObjectiveID) { + if (mapInfo == null && adlseqMapInfo == null) { + return null; + } + if (objectiveID == null || targetObjectiveID == null) { + return null; + } + if (mapInfo != null && !targetObjectiveID.equals(mapInfo.getTargetObjectiveID().getValue())) { + return null; + } + if (adlseqMapInfo != null && !targetObjectiveID.equals(adlseqMapInfo.getTargetObjectiveID().getValue())) { + return null; + } + ObjectiveMap objectiveMap = new ObjectiveMap(objectiveID, targetObjectiveID); + if (mapInfo != null) { + objectiveMap.setReadObjectiveSatisfiedStatus(mapInfo.isReadSatisfiedStatus()); + objectiveMap.setReadObjectiveNormalizedMeasure(mapInfo.isReadNormalizedMeasure()); + objectiveMap.setWriteObjectiveSatisfiedStatus(mapInfo.isWriteSatisfiedStatus()); + objectiveMap.setWriteObjectiveNormalizedMeasure(mapInfo.isWriteNormalizedMeasure()); + } + if (adlseqMapInfo != null) { + objectiveMap.setReadRawScore(adlseqMapInfo.isReadRawScore()); + objectiveMap.setReadMinScore(adlseqMapInfo.isReadMinScore()); + objectiveMap.setReadMaxScore(adlseqMapInfo.isReadMaxScore()); + objectiveMap.setReadCompletionStatus(adlseqMapInfo.isReadCompletionStatus()); + objectiveMap.setReadProgressMeasure(adlseqMapInfo.isReadProgressMeasure()); + objectiveMap.setWriteRawScore(adlseqMapInfo.isWriteRawScore()); + objectiveMap.setWriteMinScore(adlseqMapInfo.isWriteMinScore()); + objectiveMap.setWriteMaxScore(adlseqMapInfo.isWriteMaxScore()); + objectiveMap.setWriteCompletionStatus(adlseqMapInfo.isWriteCompletionStatus()); + objectiveMap.setWriteProgressMeasure(adlseqMapInfo.isWriteProgressMeasure()); + } + return objectiveMap; + } + + private static SequencingRuleDescription deriveSequencingRuleDescriptionFrom( + ConditionRule conditionRule, ConditionType conditionType) { + SequencingRuleDescription description = new SequencingRuleDescription(conditionType); + // conditionCombination + switch (conditionRule.getRuleConditions().getConditionCombination().getValue()) { + case "all": + description.getConditionCombination().setValue("All"); + break; + case "any": + description.getConditionCombination().setValue("Any"); + break; + } + // ruleConditions + for (com.xboe.module.scorm.cam.model.RuleCondition condition : conditionRule.getRuleConditions().getRuleConditionList()) { + RuleCondition ruleCondition = new RuleCondition(); + switch (condition.getCondition().getValue()) { + case "satisfied": + ruleCondition.getRuleCondition().setValue("Satisfied"); + break; + case "objectiveStatusKnown": + ruleCondition.getRuleCondition().setValue("Objective Status Known"); + break; + case "objectiveMeasureKnown": + ruleCondition.getRuleCondition().setValue("Objective Measure Known"); + break; + case "objectiveMeasureGreaterThan": + ruleCondition.getRuleCondition().setValue("Objective Measure Greater Than"); + break; + case "objectiveMeasureLessThan": + ruleCondition.getRuleCondition().setValue("Objective Measure Less Than"); + break; + case "completed": + ruleCondition.getRuleCondition().setValue("Completed"); + break; + case "activityProgressKnown": + ruleCondition.getRuleCondition().setValue("Activity Progress Known"); + break; + case "attempted": + ruleCondition.getRuleCondition().setValue("Attempted"); + break; + case "attemptLimitExceeded": + ruleCondition.getRuleCondition().setValue("Attempt Limit Exceeded"); + break; + case "timeLimitExceeded": + ruleCondition.getRuleCondition().setValue("Always"); + break; + case "outsideAvailableTimeRange": + ruleCondition.getRuleCondition().setValue("Always"); + break; + case "always": + ruleCondition.getRuleCondition().setValue("Always"); + break; + } + if (StringUtils.isNotBlank(condition.getReferencedObjective())) { + ruleCondition.setReferencedObjective(condition.getReferencedObjective()); + } + if (condition.getMeasureThreshold() != null) { + ruleCondition.getMeasureThreshold().setValue(condition.getMeasureThreshold().getDecimalValue().doubleValue()); + } + switch (condition.getOperator().getValue()) { + case "not": + ruleCondition.getOperator().setValue("Not"); + break; + case "noOp": + ruleCondition.getOperator().setValue("NO-OP"); + break; + } + description.getRuleConditions().add(ruleCondition); + } + // ruleAction + if (conditionType == ConditionType.PRECONDITION) { + switch (conditionRule.getRuleAction().getAction().getValue()) { + case "skip": + description.getRuleAction().setValue("Skip"); + break; + case "disabled": + description.getRuleAction().setValue("Disabled"); + break; + case "hiddenFromChoice": + description.getRuleAction().setValue("Hidden from Choice"); + break; + case "stopForwardTraversal": + description.getRuleAction().setValue("Stop Forward Traversal"); + break; + } + } else if (conditionType == ConditionType.POSTCONDITION) { + switch (conditionRule.getRuleAction().getAction().getValue()) { + case "exitParent": + description.getRuleAction().setValue("Exit Parent"); + break; + case "exitAll": + description.getRuleAction().setValue("Exit All"); + break; + case "retry": + description.getRuleAction().setValue("Retry"); + break; + case "retryAll": + description.getRuleAction().setValue("Retry All"); + break; + case "continue": + description.getRuleAction().setValue("Continue"); + break; + case "previous": + description.getRuleAction().setValue("Previous"); + break; + } + + } else if (conditionType == ConditionType.EXITCONDITION) { + if ("exit".equals(conditionRule.getRuleAction().getAction().getValue())) { + description.getRuleAction().setValue("Exit"); + } + } + return description; + } + + private static void initPresentation(Activity activity, Presentation presentation) { + if (presentation != null) { + NavigationInterface navigationInterface = presentation.getNavigationInterface(); + if (navigationInterface != null) { + for (HideLMSUI hideLMSUI : navigationInterface.getHideLMSUIList()) { + switch (hideLMSUI.getValue()) { + case "previous": + activity.getHideLmsUIControls().setHidePrevious(true); + break; + case "continue": + activity.getHideLmsUIControls().setHideContinue(true); + break; + case "exit": + activity.getHideLmsUIControls().setHideExit(true); + break; + case "exitAll": + activity.getHideLmsUIControls().setHideExitAll(true); + break; + case "abandon": + activity.getHideLmsUIControls().setHideAbandon(true); + break; + case "abandonAll": + activity.getHideLmsUIControls().setHideAbandonAll(true); + break; + case "suspendAll": + activity.getHideLmsUIControls().setHideSuspendAll(true); + break; + } + } + } + } + } + + private static void initCompletionThreshold(Activity activity, CompletionThreshold completionThreshold) { + if (completionThreshold != null) { + activity.getSequencingDefinition().getCompletionThreshold() + .setCompletedByMeasure(completionThreshold.isCompletedByMeasure()); + if (completionThreshold.getMinProgressMeasure() != null) { + activity.getSequencingDefinition().getCompletionThreshold().getMinimumProgressMeasure() + .setValue(completionThreshold.getMinProgressMeasure().getDecimalValue().doubleValue()); + } + if (completionThreshold.getProgressWeight() != null) { + activity.getSequencingDefinition().getCompletionThreshold().getProgressWeight() + .setValue(completionThreshold.getProgressWeight().getDecimalValue().doubleValue()); + } + } + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/AttemptManager.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/AttemptManager.java new file mode 100644 index 00000000..95436d8b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/AttemptManager.java @@ -0,0 +1,218 @@ +package com.xboe.module.scorm.sn.api; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import lombok.extern.slf4j.Slf4j; + +import com.xboe.module.scorm.SCORM; +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.rte.api.LearnerAttempt; +import com.xboe.module.scorm.rte.api.SCORMRuntimeManager; +import com.xboe.module.scorm.sn.api.behavior.OverallSequencingBehavior; +import com.xboe.module.scorm.sn.api.behavior.result.OverallSequencingResult; +import com.xboe.module.scorm.sn.api.event.EventTranslator; +import com.xboe.module.scorm.sn.api.event.EventType; +import com.xboe.module.scorm.sn.api.event.NavigationEvent; +import com.xboe.module.scorm.sn.api.request.NavigationRequest; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +@Slf4j +public class AttemptManager { + + private final ID managerID; + + private final SequencingSession sequencingSession; + + private final AtomicInteger sessionCount; + + private final ActivityTree targetActivityTree; + + private ActivityAttempt activityAttempt; + + private final Map<Activity, ActivityAttempt> containedAttempts; + + private final List<EventType> processedEventTypeSeries; + + public AttemptManager(ActivityTree targetActivityTree) { + this.targetActivityTree = targetActivityTree; + this.managerID = targetActivityTree.getId(); + this.sessionCount = new AtomicInteger(0); + this.processedEventTypeSeries = new ArrayList<>(); + this.containedAttempts = new HashMap<>(); + this.sequencingSession = new SequencingSession(); + } + + public ProcessResult process(NavigationEvent event) { + SCORM.getInstance().mapRuntimeDataToTrackingInfo(targetActivityTree.getGlobalStateInformation().getCurrentActivity()); + processedEventTypeSeries.add(event.getType()); + Activity targetActivity = null; + if (StringUtils.isNotBlank(event.getTargetActivityID())) { + targetActivity = targetActivityTree.findActivityByID(generateID(event.getTargetActivityID())); + } else { + if (event.getType() == EventType.Choose || event.getType() == EventType.Jump) { + return new ProcessResult("choose or jump event expected target activity"); + } + } + NavigationRequest request = EventTranslator.translateEventToRequestType(event, targetActivityTree, targetActivity); + Activity oldCurrentActivity = targetActivityTree.getGlobalStateInformation().getCurrentActivity(); + boolean oldIsActive = oldCurrentActivity != null && oldCurrentActivity.getActivityStateInformation().isActivityIsActive(); + OverallSequencingResult result = OverallSequencingBehavior.overallSequencing(request); + if (!updateAttempt()) { + log.error("update ActivityAttempt error"); + } + if (!result.isSuccess()) { + log.error("Exception: {} - {}", result.getException().getCode(), result.getException().getDescription()); + return new ProcessResult(result.getException()); + } + if (result.isExit()) { + Activity currentActivity = targetActivityTree.getGlobalStateInformation().getCurrentActivity(); + if (currentActivity != null && !currentActivity.getActivityStateInformation().isActivityIsActive()) { + targetActivityTree.getGlobalStateInformation().setCurrentActivity(null); + } + } + if (result.isExit() && result.getEndSequencingSession()) { + if (event.getType() == EventType.SuspendAll) { + sequencingSession.suspend(); + } else { + sequencingSession.close(); + } + } + Activity currentActivity = targetActivityTree.getGlobalStateInformation().getCurrentActivity(); + if (currentActivity != null && currentActivity.isLeaf() && currentActivity.getActivityStateInformation().isActivityIsActive() + && (!currentActivity.equals(oldCurrentActivity) || !oldIsActive)) { + // has a new delivery activity + return new ProcessResult(currentActivity); + } else { + return new ProcessResult(); + } + } + + private ID generateID(String id) { + return new ID(id, managerID.getLmsContentPackageID(), managerID.getLmsLearnerID()); + } + + private boolean updateAttempt() { + if (activityAttempt == null) { + if (targetActivityTree.getRoot().getActivityStateInformation().isActivityIsActive()) { + activityAttempt = createNewAttempt(targetActivityTree.getRoot(), null); + containedAttempts.put(targetActivityTree.getRoot(), activityAttempt); + sequencingSession.establish(); + sessionCount.incrementAndGet(); + } + } + if (activityAttempt == null) { + return false; + } + List<Activity> shouldUpdateOrCreate = new ArrayList<>(); + List<Activity> shouldDelete = new ArrayList<>(); + checkAttemptState(shouldUpdateOrCreate, shouldDelete); + syncAttempt(shouldUpdateOrCreate, shouldDelete); + if (containedAttempts.isEmpty()) { + activityAttempt = null; + sequencingSession.close(); + } + return true; + } + + private void syncAttempt(List<Activity> shouldUpdateOrCreate, List<Activity> shouldDelete) { + for (Activity activity : shouldUpdateOrCreate) { + if (containedAttempts.containsKey(activity)) { + updateAttemptState(containedAttempts.get(activity), activity); + } else { + containedAttempts.put(activity, + createNewAttempt(activity, containedAttempts.get(activity.getParentActivity()))); + } + } + for (int i = shouldDelete.size() - 1; i >= 0; i--) { + Activity activity = shouldDelete.get(i); + containedAttempts.remove(activity); + LearnerAttempt learnerAttempt = SCORMRuntimeManager.getInstance().getLearnerAttempt(activity.getId()); + if (learnerAttempt != null) { + learnerAttempt.terminate(""); +// learnerAttempt.closeLearnerSession(true); + } + } + } + + private void checkAttemptState(List<Activity> shouldUpdateOrCreate, List<Activity> shouldDelete) { + List<Activity> activities = targetActivityTree.preorder(); + for (Activity child : activities) { + if (child.getActivityStateInformation().isActivityIsActive() + || child.getActivityStateInformation().isActivityIsSuspended()) { // update or create + shouldUpdateOrCreate.add(child); + } else { // delete + shouldDelete.add(child); + } + } + } + + private ActivityAttempt createNewAttempt(Activity activity, ActivityAttempt parent) { + ActivityAttempt attempt = new ActivityAttempt(activity, parent); + updateAttemptState(attempt, activity); + return attempt; + } + + private void updateAttemptState(ActivityAttempt attempt, Activity activity) { + if (activity.getActivityStateInformation().isActivityIsActive()) { + attempt.active(); + } else if (activity.getActivityStateInformation().isActivityIsSuspended()) { + attempt.suspend(); + } + } + + public ID getManagerID() { + return managerID; + } + + public SequencingSession getSequencingSession() { + return sequencingSession; + } + + public int getSessionCount() { + return sessionCount.get(); + } + + public ActivityTree getTargetActivityTree() { + return targetActivityTree; + } + + public ActivityAttempt getActivityAttempt() { + return activityAttempt; + } + + public EventType getLastProcessedEventType() { + if (processedEventTypeSeries.isEmpty()) { + return null; + } + return processedEventTypeSeries.get(processedEventTypeSeries.size() - 1); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + AttemptManager that = (AttemptManager) o; + + return new EqualsBuilder() + .append(managerID, that.managerID) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(managerID) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ProcessResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ProcessResult.java new file mode 100644 index 00000000..f99620f3 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/ProcessResult.java @@ -0,0 +1,47 @@ +package com.xboe.module.scorm.sn.api; + +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.sn.api.behavior.result.SequencingException; +import com.xboe.module.scorm.sn.model.tree.Activity; + +public final class ProcessResult { + + private final boolean success; + private final Activity deliveryActivity; + private final String errorMsg; + + public ProcessResult() { + this(true, null, ""); + } + + public ProcessResult(String errorMsg) { + this(false, null, errorMsg); + } + + public ProcessResult(Activity deliveryActivity) { + this(true, deliveryActivity, ""); + } + + public ProcessResult(SequencingException sequencingException) { + this(false, null, CommonUtils.format("Sequencing Exception: {} - {}", + sequencingException.getCode(), sequencingException.getDescription())); + } + + private ProcessResult(boolean success, Activity deliveryActivity, String errorMsg) { + this.success = success; + this.deliveryActivity = deliveryActivity; + this.errorMsg = errorMsg; + } + + public boolean isSuccess() { + return success; + } + + public Activity getDeliveryActivity() { + return deliveryActivity; + } + + public String getErrorMsg() { + return errorMsg; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/SCORMSeqNavManager.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/SCORMSeqNavManager.java new file mode 100644 index 00000000..5ddef735 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/SCORMSeqNavManager.java @@ -0,0 +1,109 @@ +package com.xboe.module.scorm.sn.api; + +import com.xboe.module.scorm.cam.load.SCORMPackageManager; +import com.xboe.module.scorm.cam.model.ContentPackage; +import com.xboe.module.scorm.common.CommonUtils; +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.rte.api.LMSLearnerInfo; +import com.xboe.module.scorm.sn.api.event.NavigationEvent; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; +import lombok.extern.slf4j.Slf4j; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +public class SCORMSeqNavManager { + + private static SCORMSeqNavManager instance; + + private SCORMPackageManager scormPackageManager; + + private Map<ID, AttemptManager> attemptManagerMap; + + private SCORMSeqNavManager() { + this.scormPackageManager = SCORMPackageManager.getInstance(); + this.attemptManagerMap = new ConcurrentHashMap<>(); + } + + public static SCORMSeqNavManager getInstance() { + if (instance == null) { + synchronized (SCORMSeqNavManager.class) { + if (instance == null) { + instance = new SCORMSeqNavManager(); + } + } + } + return instance; + } + + public boolean launch(String lmsContentPackageID, LMSLearnerInfo lmsLearnerInfo, String organizationId) { + return launch(lmsContentPackageID, lmsLearnerInfo, organizationId, false); + } + + public boolean launch(String lmsContentPackageID, LMSLearnerInfo lmsLearnerInfo, String organizationId, boolean reloadIfPresent) { + ContentPackage contentPackage = scormPackageManager.launch(lmsContentPackageID, reloadIfPresent); + ActivityTree activityTree = ActivityTreeGenerator + .deriveActivityTreesFrom(contentPackage, lmsContentPackageID, lmsLearnerInfo, organizationId); + if (activityTree == null) { + return false; + } + if (attemptManagerMap.containsKey(activityTree.getId()) && !reloadIfPresent) { + return true; + } + AttemptManager attemptManager = new AttemptManager(activityTree); + attemptManagerMap.put(attemptManager.getManagerID(), attemptManager); + return true; + } + + public void unlaunch(LMSLearnerInfo lmsLearnerInfo) { + List<ID> shouldDelete = new LinkedList<>(); + for (ID id : attemptManagerMap.keySet()) { + if (id.getLmsLearnerID().equals(lmsLearnerInfo.getLearnerID())) { + shouldDelete.add(id); + } + } + unlaunch(shouldDelete); + } + + public void unlaunch(String lmsContentPackageID) { + List<ID> shouldDelete = new LinkedList<>(); + for (ID id : attemptManagerMap.keySet()) { + if (id.getLmsContentPackageID().equals(lmsContentPackageID)) { + shouldDelete.add(id); + } + } + unlaunch(shouldDelete); + } + + public void unlaunch(String learnerID, String lmsContentPackageID) { + List<ID> shouldDelete = new LinkedList<>(); + for (ID id : attemptManagerMap.keySet()) { + if (id.getLmsLearnerID().equals(learnerID) + && id.getLmsContentPackageID().equals(lmsContentPackageID)) { + shouldDelete.add(id); + } + } + unlaunch(shouldDelete); + } + + private void unlaunch(List<ID> shouldDelete) { + for (ID id : shouldDelete) { + attemptManagerMap.remove(id); + } + } + + public ProcessResult process(ID managerID, NavigationEvent event) { + if (attemptManagerMap.containsKey(managerID)) { + return attemptManagerMap.get(managerID).process(event); + } + return new ProcessResult(CommonUtils.format("manager id \"{}\" not exist.")); + } + + public AttemptManager findAttemptManagerBy(ID id) { + return attemptManagerMap.get(id); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/SequencingSession.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/SequencingSession.java new file mode 100644 index 00000000..6e8762b8 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/SequencingSession.java @@ -0,0 +1,76 @@ +package com.xboe.module.scorm.sn.api; + +import java.util.UUID; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +public class SequencingSession { + + private final String sessionID; + + private State state; + + public SequencingSession() { + this.sessionID = UUID.randomUUID().toString(); + this.state = State.INITIALIZED; + } + + public void establish() { + state = State.ESTABLISHED; + } + + public void suspend() { + state = State.SUSPENDED; + } + + public void close() { + state = State.CLOSED; + } + + public boolean isInitialized() { + return state == State.INITIALIZED; + } + + public boolean isEstablished() { + return state == State.ESTABLISHED; + } + + public boolean isSuspended() { + return state == State.SUSPENDED; + } + + public boolean isClosed() { + return state == State.CLOSED; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + SequencingSession that = (SequencingSession) o; + + return new EqualsBuilder() + .append(sessionID, that.sessionID) + .append(state, that.state) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(sessionID) + .append(state) + .toHashCode(); + } + + public enum State { + INITIALIZED, + ESTABLISHED, + SUSPENDED, + CLOSED + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/DeliveryBehavior.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/DeliveryBehavior.java new file mode 100644 index 00000000..372d0985 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/DeliveryBehavior.java @@ -0,0 +1,233 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import java.util.LinkedList; +import java.util.List; + +import com.xboe.module.scorm.sn.api.behavior.result.DeliveryBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.SequencingException; +import com.xboe.module.scorm.sn.api.behavior.result.UtilityProcessResult; +import com.xboe.module.scorm.sn.api.request.DeliveryRequest; +import com.xboe.module.scorm.sn.api.request.UtilityProcessRequest; +import com.xboe.module.scorm.sn.model.definition.ObjectiveDescription; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class DeliveryBehavior { + + /** + * Delivery Request Process [DB.1.1] + * + * For a delivery request; returns the validity of the delivery request; may return an exception code. + * + * Reference: + * Check Activity Process UP.5 + * + * @see UtilityProcess#processCheckActivity(UtilityProcessRequest) UP.5 + */ + public static DeliveryBehaviorResult processDeliveryRequest(DeliveryRequest deliveryRequest) { + ActivityTree activityTree = deliveryRequest.getTargetActivityTree(); + Activity targetActivity = deliveryRequest.getTargetActivity(); + // 1 + // Can only deliver leaf activities. + if (!targetActivity.isLeaf()) { + // 1.1 + return new DeliveryBehaviorResult() + .setValidDeliveryRequest(false) + .setException(SequencingException.DB111); + } + // 2 + // from the root of the activity tree to the activity specified in the delivery request, inclusive + LinkedList<Activity> activityPath = new LinkedList<>(); + Activity tmp = targetActivity; + while (tmp != null) { + activityPath.addFirst(tmp); + tmp = tmp.getParentActivity(); + } + // 3 + // Nothing to deliver. + if (activityPath.isEmpty()) { + // 3.1 + return new DeliveryBehaviorResult() + .setValidDeliveryRequest(false) + .setException(SequencingException.DB112); + } + // 4 + // Make sure each activity along the path is allowed. + for (Activity activity : activityPath) { + // 4.1 + UtilityProcessResult result = UtilityProcess + .processCheckActivity(new UtilityProcessRequest(activityTree, activity)); + // 4.2 + if (result.getResult()) { + // 4.2.1 + return new DeliveryBehaviorResult() + .setValidDeliveryRequest(false) + .setException(SequencingException.DB113); + } + } + return new DeliveryBehaviorResult().setValidDeliveryRequest(true); + } + + /** + * Content Delivery Environment Process [DB.2] + * + * For a delivery request; may return an exception code. + * + * Reference: + * Activity Progress Status TM.1.2.1 + * Activity Attempt Count TM.1.2.1 + * Activity is Active AM.1.1 + * Activity is Suspended AM.1.1 + * Attempt Absolute Duration TM.1.2.2 + * Attempt Experienced Duration TM.1.2.2 + * Attempt Progress Information TM.1.2.2 + * Clear Suspended Activity Subprocess DB.2.1 + * Current Activity AM.1.2 + * Objective Progress Information TM.1.1 + * Suspended Activity AM.1.2 + * Terminate Descendent Attempts Process UP.4 + * Tracked SM.11 + * @see DeliveryBehavior#processClearSuspendedActivity(DeliveryRequest) DB.2.1 + * @see UtilityProcess#processTerminateDescendentAttempts(UtilityProcessRequest) UP.4 + */ + public static DeliveryBehaviorResult processContentDeliveryEnvironment(DeliveryRequest deliveryRequest) { + ActivityTree activityTree = deliveryRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + Activity suspendedActivity = activityTree.getGlobalStateInformation().getSuspendedActivity(); + Activity identifiedActivity = deliveryRequest.getTargetActivity(); + // 1 + // If the attempt on the current activity has not been terminated, we cannot deliver new content. + if (currentActivity != null && currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 1.1 + // Delivery request is invalid - The Current Activity has not been terminated. + return new DeliveryBehaviorResult().setException(SequencingException.DB21); + } + // 2 + // Content is about to be delivered, clear any existing suspend all state. + if (!identifiedActivity.equals(suspendedActivity)) { + // 2.1 + processClearSuspendedActivity(deliveryRequest); + } + // 3 + // Make sure that all attempts that should end are terminated. + UtilityProcess.processTerminateDescendentAttempts(new UtilityProcessRequest(activityTree, identifiedActivity)); + // 4 + // Begin all attempts required to deliver the identified activity. + LinkedList<Activity> activityPath = new LinkedList<>(); + Activity tmp = identifiedActivity; + while (tmp != null) { + activityPath.addFirst(tmp); + tmp = tmp.getParentActivity(); + } + // 5 + for (Activity activity : activityPath) { + // 5.1 + if (!activity.getActivityStateInformation().isActivityIsActive()) { + // 5.1.1 + if (activity.getSequencingDefinition().getDeliveryControls().isTracked()) { + // 5.1.1.1 + // If the previous attempt on the activity ended due to a suspension, + // clear the suspended state; do not start a new attempt. + if (activity.getActivityStateInformation().isActivityIsSuspended()) { + // 5.1.1.1.1 + activity.getActivityStateInformation().setActivityIsSuspended(false); + } else { // 5.1.1.2 + // 5.1.1.2.1 + // Begin a new attempt on the activity. + activity.getActivityProgressInformation().getActivityAttemptCount().setValue( + activity.getActivityProgressInformation().getActivityAttemptCount().getValue() + 1); + // 5.1.1.2.2 + // Is this the first attempt on the activity? + if (activity.getActivityProgressInformation().getActivityAttemptCount().getValue() == 1) { + // 5.1.1.2.2.1 + activity.getActivityProgressInformation().setActivityProgressStatus(true); + } + // 5.1.1.2.3 + // Initialize tracking information for the new attempt. + activity.getAttemptProgressInformation().reinit(); + for (ObjectiveDescription objectiveDescription : + activity.getSequencingDefinition().getObjectiveDescriptions()) { + objectiveDescription.getObjectiveProgressInformation().reinit(); + } + } + } + // 5.1.2 + activity.getActivityStateInformation().setActivityIsActive(true); + } + } + // 6 + // The activity identified for delivery becomes the current activity. + activityTree.getGlobalStateInformation().setCurrentActivity(identifiedActivity); + // 7 + activityTree.getGlobalStateInformation().setSuspendedActivity(null); + // 8 + // The delivery environment is assumed to deliver the content resources associated with the + // identified activity. While the activity is assumed to be active, the sequencer may track learner status. + // Once the delivery of the activity’s content resources and auxiliary resources begins + // 8.1 +// if (!identifiedActivity.getSequencingDefinition().getDeliveryControls().isTracked()) { + // 8.1.1 + // The Objective and Attempt Progress information for the activity should not be recorded during delivery + // 8.1.2 + // The delivery environment begins tracking the Attempt Absolute Duration and the Attempt Experienced Duration +// } + return new DeliveryBehaviorResult(); + } + + /** + * Clear Suspended Activity Subprocess [DB.2.1] + * + * For an activity; may change the Suspended Activity. + * + * Reference: + * Activity is Suspended AM.1.1 + * Suspended Activity AM.1.2 + */ + public static void processClearSuspendedActivity(DeliveryRequest deliveryRequest) { + ActivityTree activityTree = deliveryRequest.getTargetActivityTree(); + Activity suspendedActivity = activityTree.getGlobalStateInformation().getSuspendedActivity(); + Activity identifiedActivity = deliveryRequest.getTargetActivity(); + // 1 + // Make sure there is something to clear. + if (suspendedActivity != null) { + // 1.1 + Activity commonAncestor = activityTree.findCommonAncestorFor(identifiedActivity, suspendedActivity); + // 1.2 + // from the Suspended Activity to the common ancestor, inclusive + List<Activity> activityPath = new LinkedList<>(); + Activity tmp = suspendedActivity; + while (tmp != null) { + activityPath.add(tmp); + if (tmp.equals(commonAncestor)) { + break; + } + tmp = tmp.getParentActivity(); + } + // 1.3 + if (!activityPath.isEmpty()) { + // 1.3.1 + // Walk down the tree setting each of the identified activities to 'not suspended' + for (Activity activity : activityPath) { + // 1.3.1.1 + if (activity.isLeaf()) { + // 1.3.1.1.1 + activity.getActivityStateInformation().setActivityIsSuspended(false); + } else { // 1.3.1.2 + // 1.3.1.2.1 + if (activity.getChildren().stream() + .noneMatch(a -> a.getActivityStateInformation().isActivityIsSuspended())) { + // 1.3.1.2.1.1 + activity.getActivityStateInformation().setActivityIsSuspended(false); + } + } + } + } + // 1.4 + // Clear the suspended activity attribute. + activityTree.getGlobalStateInformation().setSuspendedActivity(null); + } + // 2 + // exit + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/NavigationBehavior.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/NavigationBehavior.java new file mode 100644 index 00000000..45420ccc --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/NavigationBehavior.java @@ -0,0 +1,392 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import java.util.LinkedList; +import java.util.List; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.sn.api.behavior.result.NavigationBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.SequencingException; +import com.xboe.module.scorm.sn.api.request.NavigationRequest; +import com.xboe.module.scorm.sn.api.request.NavigationRequest.Type; +import com.xboe.module.scorm.sn.api.request.SequencingRequest; +import com.xboe.module.scorm.sn.api.request.TerminationRequest; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class NavigationBehavior { + + /** + * Navigation Request Process [NB.2.1] + * + * For a navigation request and possibly a specified activity, returns the validity of the navigation request; + * may return a termination request, a sequencing request, and/or a target activity; may return an exception code. + * + * Reference: + * Current Activity AM.1.2 + * Sequencing Control Choice SM.1 + * Sequencing Control Choice Exit SM.1 + * Sequencing Control Flow SM.1 + * Sequencing Control Forward Only SM.1 + * Available Children AM.1.1 + * Suspended Activity AM.1.2 + */ + public static NavigationBehaviorResult processNavigationRequest(@NotNull NavigationRequest navigationRequest) { + NavigationRequest.Type type = navigationRequest.getRequestType(); + ActivityTree activityTree = navigationRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + Activity suspendedActivity = activityTree.getGlobalStateInformation().getSuspendedActivity(); + if (type == Type.Start) { // 1 + // 1.1 + // Make sure the sequencing session has not already begun. + if (currentActivity == null) { + // 1.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Start, activityTree, null)); + } else { // 1.2 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB211); + } + } else if (type == Type.ResumeAll) { // 2 + // 2.1 + // Make sure the sequencing session has not already begun. + if (currentActivity == null) { + // 2.1.1 + // Make sure the previous sequencing ended with a suspend all request. + if (suspendedActivity != null) { + // 2.1.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.ResumeAll, activityTree, null)); + } else { // 2.1.2 + // 2.1.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB213); + } + } else { // 2.2 + // 2.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB211); + } + } else if (type == Type.Continue) { // 3 + // 3.1 + // Make sure the sequencing session has already begun. + if (currentActivity == null) { + // 3.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB212); + } + // 3.2 + // Validate that a 'flow' sequencing request can be processed from the current activity. + if (!activityTree.isRoot(currentActivity) && currentActivity.getParentActivity() + .getSequencingDefinition().getSequencingControlMode().isSequencingControlFlow()) { + // 3.2.1 + // If the current activity has not been terminated, terminate the current the activity. + if (currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 3.2.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.Exit, activityTree, null)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Continue, activityTree, null)); + } else { // 3.2.2 + // 3.2.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Continue, activityTree, null)); + } + } else { // 3.3 + // 3.3.1 + // Flow is not enabled or the current activity is the root of the activity tree. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB214); + } + } else if (type == Type.Previous) { // 4 + // 4.1 + // Make sure the sequencing session has already begun. + if (currentActivity == null) { + // 4.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB212); + } + // 4.2 + // There is no activity logically 'previous' to the root of the activity tree. + if (!activityTree.isRoot(currentActivity)) { + // 4.2.1 + // Validate the a 'flow' sequencing request can be processed from the current activity. + if (currentActivity.getParentActivity().getSequencingDefinition() + .getSequencingControlMode().isSequencingControlFlow() + && !currentActivity.getParentActivity().getSequencingDefinition() + .getSequencingControlMode().isSequencingControlForwardOnly()) { + // 4.2.1.1 + // If the current activity has not been terminated, terminate the current the activity. + if (currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 4.2.1.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.Exit, activityTree, null)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Previous, activityTree, null)); + } else { // 4.2.1.2 + // 4.2.1.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Previous, activityTree, null)); + } + } else { // 4.2.2 + // 4.2.2.1 + // Violates control mode. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB215); + } + } else { // 4.3 + // 4.3.1 + // Cannot move backward from the root of the activity tree. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB216); + } + } else if (type == Type.Forward) { // 5 Behavior not defined. + // 5.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB217); + } else if (type == Type.Backward) { // 6 Behavior not defined. + // 6.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB217); + } else if (type == Type.Choice) { // 7 + // 7.1 + // Make sure the target activity exists in the activity tree. + Activity targetActivity = navigationRequest.getTargetActivity(); + if (activityTree.existActivity(targetActivity)) { + // 7.1.1 + // Validate that a 'choice' sequencing request can be processed on the target activity. + if (activityTree.isRoot(targetActivity) || targetActivity.getParentActivity() + .getSequencingDefinition().getSequencingControlMode().isSequencingControlChoice()) { + // 7.1.1.1 + // Attempt to start the sequencing session through choice + if (currentActivity == null) { + // 7.1.1.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setSequencingRequest( + new SequencingRequest(SequencingRequest.Type.Choice, activityTree, targetActivity)); + } + // 7.1.1.2 + if (!currentActivity.isSiblingActivity(targetActivity)) { + // 7.1.1.2.1 + Activity commonAncestor = activityTree.findCommonAncestorFor(currentActivity, targetActivity); + // 7.1.1.2.2 + // The common ancestor will not terminate as a result of processing the choice sequencing + // request, unless the common ancestor is the Current Activity - the current activity should + // always be included in the activity path. + // From current activity to common ancestor + List<Activity> activityPath = new LinkedList<>(); + if (commonAncestor != null) { + Activity tmp = currentActivity; + while (tmp != null) { + activityPath.add(tmp); + if (tmp.equals(commonAncestor)) { + break; + } + tmp = tmp.getParentActivity(); + } + } + // 7.1.1.2.3 + if (!activityPath.isEmpty()) { + // 7.1.1.2.3.1 + // Make sure that 'choosing' the target will not force an active activity to terminate, + // if that activity does not allow choice to terminate it. + for (Activity activity : activityPath) { + // 7.1.1.2.3.1.1 + if (activity.getActivityStateInformation().isActivityIsActive() + && !activity.getSequencingDefinition().getSequencingControlMode() + .isSequencingControlChoiceExit()) { + // 7.1.1.2.3.1.1.1 + // Violates control mode. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB218); + } + } + } else { // 7.1.1.2.4 + // 7.1.1.2.4.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB219); + } + } + // 7.1.1.3 + // The Choice target is a sibling to the Current Activity, check if the Current Activity + // is allowed to exit. + if (currentActivity.getActivityStateInformation().isActivityIsActive() + && !currentActivity.getSequencingDefinition().getSequencingControlMode() + .isSequencingControlChoiceExit()) { + // 7.1.1.3.1 + // Violates control mode. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB218); + } + // 7.1.1.4 + // If the current activity has not been terminated, terminate the current the activity. + if (currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 7.1.1.4.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.Exit, activityTree, null)) + .setSequencingRequest( + new SequencingRequest(SequencingRequest.Type.Choice, activityTree, targetActivity)); + } else { // 7.1.1.5 + // 7.1.1.5.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setSequencingRequest( + new SequencingRequest(SequencingRequest.Type.Choice, activityTree, targetActivity)); + } + } else { // 7.1.2 + // 7.1.2.1 + // Violates control mode. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB2110); + } + } else { // 7.2 + // 7.2.1 + // Target activity does not exist. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB2111); + } + } else if (type == Type.Exit) { // 8 + // 8.1 + // Make sure the sequencing session has already begun. + if (currentActivity != null) { + // 8.1.1 + // Make sure the current activity has not already been terminated. + if (currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 8.1.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.Exit, activityTree, null)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, null)); + } else { // 8.1.2 + // 8.1.2.1 + // Activity has already terminated. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB2112); + } + } else { // 8.2 + // 8.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB212); + } + } else if (type == Type.ExitAll) { // 9 + // 9.1 + // If the sequencing session has already begun, unconditionally terminate all active activities. + if (currentActivity != null) { + // 9.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.ExitAll, activityTree, null)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, null)); + } else { // 9.2 + // 9.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB212); + } + } else if (type == Type.Abandon) { // 10 + // 10.1 + // Make sure the sequencing session has already begun. + if (currentActivity != null) { + // 10.1.1 + // Make sure the current activity has not already been terminated. + if (currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 10.1.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.Abandon, activityTree, null)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, null)); + } else { // 10.1.2 + // 10.1.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB2112); + } + } else { // 10.2 + // 10.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB212); + } + } else if (type == Type.AbandonAll) { // 11 + // 11.1 + // If the sequencing session has already begun, unconditionally abandon all active activities. + if (currentActivity != null) { + // 11.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.AbandonAll, activityTree, null)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, null)); + } else { // 11.2 + // 11.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB212); + } + } else if (type == Type.SuspendAll) { // 12 + // 12.1 + // If the sequencing session has already begun. + if (currentActivity != null) { + // 12.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.SuspendAll, activityTree, null)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, null)); + } else { // 12.2 + // 12.2.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB212); + } + } else if (type == Type.Jump) { // 13 + // 13.1 + // Make sure the target activity exists in the activity tree and is available. + Activity targetActivity = navigationRequest.getTargetActivity(); + if (activityTree.existActivity(targetActivity) + && targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().contains(targetActivity)) { + // 13.1.1 + return new NavigationBehaviorResult() + .setValidNavigationRequest(true) + .setTerminationRequest(new TerminationRequest(TerminationRequest.Type.Exit, activityTree, null)) + .setSequencingRequest( + new SequencingRequest(SequencingRequest.Type.Jump, activityTree, targetActivity)); + } else { // 13.2 + // 13.2.1 + // Target activity does not exist. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB2111); + } + } + + // 14 + // Undefined navigation request. + return new NavigationBehaviorResult() + .setValidNavigationRequest(false) + .setException(SequencingException.NB2113); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/OverallSequencingBehavior.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/OverallSequencingBehavior.java new file mode 100644 index 00000000..5dd1a1e8 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/OverallSequencingBehavior.java @@ -0,0 +1,121 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.sn.api.behavior.result.DeliveryBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.NavigationBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.OverallSequencingResult; +import com.xboe.module.scorm.sn.api.behavior.result.SequencingBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.TerminationBehaviorResult; +import com.xboe.module.scorm.sn.api.request.DeliveryRequest; +import com.xboe.module.scorm.sn.api.request.NavigationRequest; +import com.xboe.module.scorm.sn.api.request.SequencingRequest; +import com.xboe.module.scorm.sn.api.request.TerminationRequest; + +public class OverallSequencingBehavior { + + /** + * Overall Sequencing Process [OP.1] + * + * Reference: + * Content Delivery Environment Process DB.2 + * Delivery Request Process DB.1.1 + * Navigation Request Process NB.2.1 + * Sequencing Request Process SB.2.12 + * Termination Request Process TB.2.3 + * + * @see DeliveryBehavior#processContentDeliveryEnvironment(DeliveryRequest) DB.2 + * @see DeliveryBehavior#processDeliveryRequest(DeliveryRequest) DB.1.1 + * @see NavigationBehavior#processNavigationRequest(NavigationRequest) NB.2.1 + * @see SequencingBehavior#processSequencingRequest(SequencingRequest) SB.2.12 + * @see TerminationBehavior#processTerminationRequest(TerminationRequest) TB.2.3 + */ + public static OverallSequencingResult overallSequencing(@NotNull NavigationRequest navigationRequest) { + // 1.1 + NavigationBehaviorResult navigationBehaviorResult = NavigationBehavior.processNavigationRequest(navigationRequest); + // 1.2 + if (!navigationBehaviorResult.isValidNavigationRequest()) { + // 1.2.1 & 1.2.2 + // Behavior not specified. + return new OverallSequencingResult(false) + .setException(navigationBehaviorResult.getException()); + } + + SequencingRequest sequencingRequest = navigationBehaviorResult.getSequencingRequest(); + + // 1.3 + // If the current activity is active, end the attempt on the current activity. + if (navigationBehaviorResult.getTerminationRequest() != null) { + navigationBehaviorResult.getTerminationRequest().setTargetActivityTree(navigationRequest.getTargetActivityTree()); + // 1.3.1 + TerminationBehaviorResult terminationBehaviorResult = TerminationBehavior.processTerminationRequest( + navigationBehaviorResult.getTerminationRequest()); + // 1.3.2 + if (!terminationBehaviorResult.isValidTerminationRequest()) { + // 1.3.2.1 & 1.3.2.2 + // Behavior not specified. + return new OverallSequencingResult(false) + .setException(terminationBehaviorResult.getException()); + } + // 1.3.3 + if (terminationBehaviorResult.getSequencingRequest() != null) { + // 1.3.3.1 + // There can only be one pending sequencing request. + // Use the one returned by the termination request process, if it exists. + sequencingRequest = terminationBehaviorResult.getSequencingRequest(); + } + } + + // 1.4 + DeliveryRequest deliveryRequest = null; + if (sequencingRequest != null) { + // 1.4.1 + SequencingBehaviorResult sequencingBehaviorResult = SequencingBehavior.processSequencingRequest(sequencingRequest); + // 1.4.2 + if (!sequencingBehaviorResult.isValidSequencingRequest()) { + // 1.4.2.1 & 1.4.2.2 + // Behavior not specified + return new OverallSequencingResult(false) + .setException(sequencingBehaviorResult.getException()); + } + // 1.4.3 + if (sequencingBehaviorResult.getEndSequencingSession() != null) { + // 1.4.3.1 + // Exiting from the root of the activity tree ends the sequencing session; + // return control to the LTS + return new OverallSequencingResult(true, true) + .setEndSequencingSession(sequencingBehaviorResult.getEndSequencingSession()); + } + // 1.4.4 + if (sequencingBehaviorResult.getDeliveryRequest() == null) { + // 1.4.4.1 + return new OverallSequencingResult(true); + } + // 1.4.5 + // delivery request is for the activity identified by the Sequencing Request Process + deliveryRequest = sequencingBehaviorResult.getDeliveryRequest(); + } + + // 1.5 + if (deliveryRequest != null) { + // 1.5.1 + DeliveryBehaviorResult deliveryBehaviorResult = DeliveryBehavior.processDeliveryRequest(deliveryRequest); + // 1.5.2 + if (!deliveryBehaviorResult.isValidDeliveryRequest()) { + // 1.5.2.1 & 1.5.2.2 + // Behavior not specified + return new OverallSequencingResult(false) + .setException(deliveryBehaviorResult.getException()); + } + // 1.5.3 + deliveryBehaviorResult = DeliveryBehavior.processContentDeliveryEnvironment(deliveryRequest); + if (deliveryBehaviorResult.getException() != null) { + return new OverallSequencingResult(false) + .setException(deliveryBehaviorResult.getException()); + } + } + + return new OverallSequencingResult(true); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/RollupBehavior.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/RollupBehavior.java new file mode 100644 index 00000000..40503534 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/RollupBehavior.java @@ -0,0 +1,894 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import com.xboe.module.scorm.sn.api.behavior.result.RollupBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.UtilityProcessResult; +import com.xboe.module.scorm.sn.api.request.RollupRequest; +import com.xboe.module.scorm.sn.api.request.UtilityProcessRequest; +import com.xboe.module.scorm.sn.api.util.BooleanUtils; +import com.xboe.module.scorm.sn.model.definition.LimitConditions; +import com.xboe.module.scorm.sn.model.definition.ObjectiveDescription; +import com.xboe.module.scorm.sn.model.definition.RollupCondition; +import com.xboe.module.scorm.sn.model.definition.RollupRuleDescription; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription.ConditionType; +import com.xboe.module.scorm.sn.model.tracking.ActivityProgressInformation; +import com.xboe.module.scorm.sn.model.tracking.AttemptProgressInformation; +import com.xboe.module.scorm.sn.model.tracking.ObjectiveProgressInformation; +import com.xboe.module.scorm.sn.model.tree.Activity; + +public class RollupBehavior { + + private static final RollupRuleDescription DEFAULT_ROLLUP_RULE_SATISFIED; + private static final RollupRuleDescription DEFAULT_ROLLUP_RULE_NOT_SATISFIED; + private static final RollupRuleDescription DEFAULT_ROLLUP_RULE_COMPLETED; + private static final RollupRuleDescription DEFAULT_ROLLUP_RULE_INCOMPLETE; + + static { + DEFAULT_ROLLUP_RULE_SATISFIED = new RollupRuleDescription(); + DEFAULT_ROLLUP_RULE_SATISFIED.getChildActivitySet().setValue("All"); + RollupCondition rollupCondition = new RollupCondition(); + rollupCondition.getRollupCondition().setValue("Satisfied"); + DEFAULT_ROLLUP_RULE_SATISFIED.getRollupConditions().add(rollupCondition); + DEFAULT_ROLLUP_RULE_SATISFIED.getRollupAction().setValue("Satisfied"); + + DEFAULT_ROLLUP_RULE_NOT_SATISFIED = new RollupRuleDescription(); + DEFAULT_ROLLUP_RULE_NOT_SATISFIED.getChildActivitySet().setValue("All"); + rollupCondition = new RollupCondition(); + rollupCondition.getRollupCondition().setValue("Objective Status Known"); + DEFAULT_ROLLUP_RULE_NOT_SATISFIED.getRollupConditions().add(rollupCondition); + DEFAULT_ROLLUP_RULE_NOT_SATISFIED.getRollupAction().setValue("Not Satisfied"); + + DEFAULT_ROLLUP_RULE_COMPLETED = new RollupRuleDescription(); + DEFAULT_ROLLUP_RULE_COMPLETED.getChildActivitySet().setValue("All"); + rollupCondition = new RollupCondition(); + rollupCondition.getRollupCondition().setValue("Completed"); + DEFAULT_ROLLUP_RULE_COMPLETED.getRollupConditions().add(rollupCondition); + DEFAULT_ROLLUP_RULE_COMPLETED.getRollupAction().setValue("Completed"); + + DEFAULT_ROLLUP_RULE_INCOMPLETE = new RollupRuleDescription(); + DEFAULT_ROLLUP_RULE_INCOMPLETE.getChildActivitySet().setValue("All"); + rollupCondition = new RollupCondition(); + rollupCondition.getRollupCondition().setValue("Activity Progress Known"); + DEFAULT_ROLLUP_RULE_INCOMPLETE.getRollupConditions().add(rollupCondition); + DEFAULT_ROLLUP_RULE_INCOMPLETE.getRollupAction().setValue("Incomplete"); + } + + + /** + * Measure Rollup Process [RB.1.1a] + * + * For an activity; may change the Objective Information for the activity. + * + * Reference: + * Objective Contributes To Rollup SM.6 + * Objective Description SM.6 + * Objective Measure Status TM.1.1 + * Objective Normalized Measure TM.1.1 + * Rollup Objective Measure Weight SM.8 + * Tracked SM.11 + */ + public static void processMeasureRollup(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + // 1 + BigDecimal totalWeightedMeasure = new BigDecimal(0).setScale(4, BigDecimal.ROUND_HALF_UP); + // 2 + boolean validData = false; + + // 3 + BigDecimal countedMeasures = new BigDecimal(0).setScale(4, BigDecimal.ROUND_HALF_UP); + // 4 + ObjectiveDescription targetObjective = null; + // 5 + for (ObjectiveDescription objective : targetActivity.getSequencingDefinition().getObjectiveDescriptions()) { + // 5.1 + // Find the target objective for the rolled-up measure + if (objective.isObjectiveContributesToRollup()) { + // 5.1.1 + targetObjective = objective; + // 5.1.2 + break; + } + } + // 6 + if (targetObjective != null) { + // 6.1 + for (Activity child : targetActivity.getChildren()) { + // 6.1.1 + // Only include tracked children. + if (child.getSequencingDefinition().getDeliveryControls().isTracked()) { + // 6.1.1.1 + ObjectiveDescription rolledUpObjective = null; + // 6.1.1.2 + for (ObjectiveDescription objective : child.getSequencingDefinition().getObjectiveDescriptions()) { + // 6.1.1.2.1 + if (objective.isObjectiveContributesToRollup()) { + // 6.1.1.2.1.1 + rolledUpObjective = objective; + break; + } + } + // 6.1.1.3 + if (rolledUpObjective != null) { + // 6.1.1.3.1 + countedMeasures = countedMeasures.add(child.getSequencingDefinition() + .getRollupControls().getRollupObjectiveMeasureWeight().getValue()); + // 6.1.1.3.2 + if (rolledUpObjective.getObjectiveProgressInformation().isObjectiveMeasureStatus()) { + // 6.1.1.3.2.1 + totalWeightedMeasure = totalWeightedMeasure.add( + rolledUpObjective.getObjectiveProgressInformation() + .getObjectiveNormalizedMeasure().getValue().multiply( + child.getSequencingDefinition().getRollupControls() + .getRollupObjectiveMeasureWeight().getValue())); + // 6.1.1.3.2.2 + validData = true; + } + } else { // 6.1.1.4 + // 6.1.1.4.1 + // One of the children dose not include a rolled-up objective. + return; + } + } + } + // 6.2 + if (!validData) { + // 6.2.1 + // No tracking state rolled-up, cannot determine the rolled-up measure. + targetObjective.getObjectiveProgressInformation().setObjectiveMeasureStatus(false); + } else { // 6.3 + // 6.3.1 + // Set the rolled-up measure for the target objective. + if (countedMeasures.compareTo(BigDecimal.ZERO) > 0) { + // 6.3.1.1 + targetObjective.getObjectiveProgressInformation().setObjectiveMeasureStatus(true); + // 6.3.1.2 + // total / counted + targetObjective.getObjectiveProgressInformation().setObjectiveNormalizedMeasure( + totalWeightedMeasure.divide(countedMeasures, BigDecimal.ROUND_HALF_UP).doubleValue()); + } else { // 6.3.2 + // 6.3.2.1 + // No children contributed weight. + targetObjective.getObjectiveProgressInformation().setObjectiveMeasureStatus(false); + } + } + } + // 7 + // No objective contributes to rollup, so we cannot set anything. + } + + /** + * Completion Measure Rollup Process [RB.1.1b] + * + * For an activity; may change the Objective Information for the activity. + * + * Reference: + * Attempt Completion Amount Status TM.1.1 + * Attempt Completion Amount TM.1.1 + * Tracked SM.11 + * adlcp:minProgressMeasure + * adlcp:progressWeight + */ + public static void processCompletionMeasureRollup(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + // 1 + BigDecimal totalWeightedMeasure = new BigDecimal(0).setScale(4, BigDecimal.ROUND_HALF_UP); + // 2 + boolean validData = false; + // 3 + BigDecimal countedMeasure = new BigDecimal(0).setScale(4, BigDecimal.ROUND_HALF_UP); + // 4 + for (Activity child : targetActivity.getChildren()) { + // 4.1 + // Only include tracked children. + if (child.getSequencingDefinition().getDeliveryControls().isTracked()) { + // 4.1.1 + // The child is included, account for it in weighted average + countedMeasure = countedMeasure.add(child.getSequencingDefinition().getCompletionThreshold() + .getProgressWeight().getValue()); + // 4.1.2 + if (child.getAttemptProgressInformation().isAttemptCompletionAmountStatus()) { + // 4.1.2.1 + // Only include progress that has been reported or previously rolled-up. + totalWeightedMeasure = totalWeightedMeasure.add(child.getAttemptProgressInformation() + .getAttemptCompletionAmount().getValue().multiply(child.getSequencingDefinition() + .getCompletionThreshold().getProgressWeight().getValue())); + // 4.1.2.2 + validData = true; + } + } + } + // 5 + if (!validData) { + // 5.1 + // No progress state rolled-up, cannot determine the rolled-up progress. + targetActivity.getAttemptProgressInformation().setAttemptCompletionAmountStatus(false); + } else { // 5.2 + // 5.2.1 + // Set the rolled-up progress. + if (countedMeasure.compareTo(BigDecimal.ZERO) > 0) { + // 5.2.1.1 + targetActivity.getAttemptProgressInformation().setAttemptCompletionAmountStatus(true); + // 5.2.1.2 + targetActivity.getAttemptProgressInformation().getAttemptCompletionAmount().setValue( + totalWeightedMeasure.divide(countedMeasure, BigDecimal.ROUND_HALF_UP).doubleValue()); + } else { // 5.2.2 + // 5.2.2.1 + // No children contributed weight. + targetActivity.getAttemptProgressInformation().setAttemptCompletionAmountStatus(false); + } + } + } + + /** + * Objective Rollup Using Measure Process [RB.1.2a] + * + * For an activity; may change the Objective Information for the activity. + * + * Reference: + * Objective Contributes to Rollup SM.6 + * Objective Description SM.6 + * Objective Satisfied by Measure SM.6 + * Objective Measure Status TM.1.1 + * Objective Normalized Measure TM.1.1 + * Objective Progress Status TM.1.1 + * Objective Satisfied Status TM.1.1 + * Activity is Active AM.1.1 + * adlseq:measureSatisfactionIfActive SCORM SN + */ + public static void processObjectiveRollupUsingMeasureProcess(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + // 1 + ObjectiveDescription targetObjective = null; + // 2 + for (ObjectiveDescription objective : targetActivity.getSequencingDefinition().getObjectiveDescriptions()) { + // 2.1 + // Identify the objective that may be altered based on the activity's children's rolled-up measure. + if (objective.isObjectiveContributesToRollup()) { + // 2.1.1 + targetObjective = objective; + // 2.1.2 + break; + } + } + // 3 + if (targetObjective != null) { + // 3.1 + // If the objective is satisfied by measure, test the rolled-up measure against the defined threshold. + if (targetObjective.isObjectiveSatisfiedByMeasure()) { + // 3.1.1 + // No Measure known, so objective status is unreliable. + if (!targetObjective.getObjectiveProgressInformation().isObjectiveMeasureStatus()) { + // 3.1.1.1 + targetObjective.getObjectiveProgressInformation().setObjectiveProgressStatus(false); + } else { // 3.1.2 + // 3.1.2.1 + if (!targetActivity.getActivityStateInformation().isActivityIsActive() + || (targetActivity.getActivityStateInformation().isActivityIsActive() + && targetActivity.getSequencingDefinition().getRollupConsiderationControls() + .isMeasureSatisfactionIfActive())) { + // 3.1.2.1.1 + if (targetObjective.getObjectiveProgressInformation().getObjectiveNormalizedMeasure().getValue() + .compareTo(targetObjective.getObjectiveMinimumSatisfiedNormalizedMeasure().getValue()) >= 0) { + // 3.1.2.1.1.1 + targetObjective.getObjectiveProgressInformation().setObjectiveProgressStatus(true); + // 3.1.2.1.1.2 + targetObjective.getObjectiveProgressInformation().setObjectiveSatisfiedStatus(true); + } else { // 3.1.2.1.2 + // 3.1.2.1.2.1 + targetObjective.getObjectiveProgressInformation().setObjectiveProgressStatus(true); + // 3.1.2.1.2.2 + targetObjective.getObjectiveProgressInformation().setObjectiveSatisfiedStatus(false); + } + } else { // 3.1.2.2 + // 3.1.2.2.1 + // Incomplete information, do not evaluate objective status. + targetObjective.getObjectiveProgressInformation().setObjectiveProgressStatus(false); + } + } + } + // 3.2 + // exit + } + // 4 + // 4.1 + // No objective contributes to rollup, so we cannot set anything. + // exit + } + + /** + * Objective Rollup Using Rules Process [RB.1.2b] + * + * For an activity; may change the Objective Information for the activity. + * + * Reference: + * Objective Contributes to Rollup SM.6 + * Objective Description SM.6 + * Objective Progress Status TM.1.1 + * Objective Satisfied Status TM.1.1 + * Rollup Rule Check Subprocess RB.1.4 + * Rollup Action SM.5 + * Rollup Condition SM.5 + * Rollup Action SM.5 + * Rollup Child Activity Set SM.5 + * + * @see RollupBehavior#checkRollupRule(RollupRequest) RB.1.4 + */ + public static void processObjectiveRollupUsingRules(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + // 1 + // If no objective rollup rules are defined, use the default rollup rules. + boolean useDefault = true; + for (RollupRuleDescription rollupRule : targetActivity.getSequencingDefinition().getRollupRuleDescriptions()) { + if ("Satisfied".equals(rollupRule.getRollupAction().getValue()) + || "Not Satisfied".equals(rollupRule.getRollupAction().getValue())) { + useDefault = false; + break; + } + } + // 2 + ObjectiveDescription targetObjective = null; + // 3 + for (ObjectiveDescription objective : targetActivity.getSequencingDefinition().getObjectiveDescriptions()) { + // 3.1 + // Identify the objective that may be altered based on the activity's children's rolled-up status. + if (objective.isObjectiveContributesToRollup()) { + // 3.1.1 + targetObjective = objective; + // 3.1.2 + break; + } + } + // 4 + if (targetObjective != null) { + // 4.1 + // Process all Not satisfied rules first. + RollupBehaviorResult notSatisfiedResult = checkRollupRule( + new RollupRequest(rollupRequest.getTargetActivityTree(), targetActivity) + .setRollupAction("Not Satisfied") + .setUseDefaultRollupRule(useDefault) + .setDefaultRollupRule(DEFAULT_ROLLUP_RULE_NOT_SATISFIED)); + // 4.2 + if (notSatisfiedResult.getEvaluation()) { + // 4.2.1 + targetObjective.getObjectiveProgressInformation().setObjectiveProgressStatus(true); + // 4.2.2 + targetObjective.getObjectiveProgressInformation().setObjectiveSatisfiedStatus(false); + } + // 4.3 + // Process all Satisfied + RollupBehaviorResult satisfiedResult = checkRollupRule( + new RollupRequest(rollupRequest.getTargetActivityTree(), targetActivity) + .setRollupAction("Satisfied") + .setUseDefaultRollupRule(useDefault) + .setDefaultRollupRule(DEFAULT_ROLLUP_RULE_SATISFIED)); + // 4.4 + if (satisfiedResult.getEvaluation()) { + // 4.4.1 + targetObjective.getObjectiveProgressInformation().setObjectiveProgressStatus(true); + targetObjective.getObjectiveProgressInformation().setObjectiveSatisfiedStatus(true); + } + // 4.5 + // exit + } + // 5 & 5.1 + // exit + // No objective contributes to rollup, so we cannot set anything. + } + + /** + * Activity Progress Rollup Using Measure Process [RB.1.3a] + * + * For an activity; may change the Attempt Information for the activity. + * + * Reference: + * Attempt Completion Status TM.1.2.2 + * Attempt Progress Status TM.1.2.2 + * Attempt Completion Amount Status TM.1.1 + * Attempt Completion Amount TM.1.1 + * adlcp:completedByMeasure SCORM CAN + * adlcp:minProgressMeasure SCORM CAM + */ + public static void processActivityProgressRollupUsingMeasure(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + AttemptProgressInformation attemptProgressInformation = targetActivity.getAttemptProgressInformation(); + // 1 + attemptProgressInformation.setAttemptProgressStatus(false); + // 2 + attemptProgressInformation.setAttemptCompletionStatus(false); + // 3 + // If the completion is determined by measure, test the rolled-up progress against the defined threshold. + if (targetActivity.getSequencingDefinition().getCompletionThreshold().isCompletedByMeasure()) { + // 3.1 + // No progress amount known, so the status is unreliable. + if (!attemptProgressInformation.isAttemptCompletionAmountStatus()) { + // 3.1.1 + attemptProgressInformation.setAttemptCompletionStatus(false); + } else { // 3.2 + // 3.2.1 + if (attemptProgressInformation.getAttemptCompletionAmount().getValue().compareTo(targetActivity + .getSequencingDefinition().getCompletionThreshold().getMinimumProgressMeasure().getValue()) >= 0) { + // 3.2.1.1 + attemptProgressInformation.setAttemptProgressStatus(true); + attemptProgressInformation.setAttemptCompletionStatus(true); + } else { + attemptProgressInformation.setAttemptProgressStatus(true); + attemptProgressInformation.setAttemptCompletionStatus(false); + } + } + } else { // 4 + // 4.1 + // Incomplete information, do not evaluate completion status + attemptProgressInformation.setAttemptProgressStatus(false); + } + // 5 + //exit + } + + /** + * Activity Progress Rollup Using Rules [RB.1.3b] + * + * For an activity; may change the attempt information for the activity. + * + * Reference: + * Attempt Completion Status TM.1.2.2 + * Attempt Progress Status TM.1.2.2 + * Rollup Rule Check Subprocess RB.1.4 + * Rollup Action SM.5 + * Rollup Condition SM.5 + * Rollup Action SM.5 + * Rollup Child Activity Set SM.5 + * + * @see RollupBehavior#checkRollupRule(RollupRequest) RB.1.4 + */ + public static void processActivityProgressRollupUsingRules(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + AttemptProgressInformation attemptProgressInformation = targetActivity.getAttemptProgressInformation(); + // 1 & 1.1 & 1.2 + // If no objective rollup rules are defined, use the default rollup rules. + boolean useDefault = true; + for (RollupRuleDescription rollupRule : targetActivity.getSequencingDefinition().getRollupRuleDescriptions()) { + if ("Completed".equals(rollupRule.getRollupAction().getValue()) + || "Incomplete".equals(rollupRule.getRollupAction().getValue())) { + useDefault = false; + break; + } + } + // 2 + // Process all Incomplete rules first. + RollupBehaviorResult IncompleteResult = checkRollupRule( + new RollupRequest(rollupRequest.getTargetActivityTree(), targetActivity) + .setRollupAction("Incomplete") + .setUseDefaultRollupRule(useDefault) + .setDefaultRollupRule(DEFAULT_ROLLUP_RULE_INCOMPLETE)); + // 3 + if (IncompleteResult.getEvaluation()) { + // 3.1 + attemptProgressInformation.setAttemptProgressStatus(true); + // 3.2 + attemptProgressInformation.setAttemptCompletionStatus(false); + } + // 4 + RollupBehaviorResult completedResult = checkRollupRule( + new RollupRequest(rollupRequest.getTargetActivityTree(), targetActivity) + .setRollupAction("Completed") + .setUseDefaultRollupRule(useDefault) + .setDefaultRollupRule(DEFAULT_ROLLUP_RULE_COMPLETED)); + // 5 + if (completedResult.getEvaluation()) { + // 5.1 + attemptProgressInformation.setAttemptProgressStatus(true); + // 5.2 + attemptProgressInformation.setAttemptCompletionStatus(true); + } + // 6 + } + + /** + * Rollup Rule Check Subprocess [RB.1.4] + * + * For an activity and a Rollup Action; returns True if the action applies. + * + * Reference: + * Check Child for Rollup Subprocess RB.1.4.2 + * Evaluate Rollup Conditions Subprocess RB.1.4.1 + * Rollup Action SM.5 + * Rollup Child Activity Set SM.5 + * Rollup Minimum Count SM.5 + * Rollup Minimum Percent SM.5 + * Rollup Rule Description SM.5 + * Tracked SM.11 + * Tracking Model TM + * + * @see RollupBehavior#checkChildForRollup(RollupRequest) RB.1.4.2 + * @see RollupBehavior#evaluateRollupConditions(RollupRequest) RB.1.4.1 + */ + public static RollupBehaviorResult checkRollupRule(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + // 1 & 1.1 + // Make sure the activity rules to evaluate + List<RollupRuleDescription> ruleList = new LinkedList<>(); + if (!rollupRequest.isUseDefaultRollupRule()) { + for (RollupRuleDescription rollupRule : targetActivity.getSequencingDefinition().getRollupRuleDescriptions()) { + if (rollupRule.getRollupAction().getValue().equals(rollupRequest.getRollupAction())) { + ruleList.add(rollupRule); + } + } + } else { + ruleList.add(rollupRequest.getDefaultRollupRule()); + } + if (!ruleList.isEmpty()) { + // 1.2 + for (RollupRuleDescription rule : ruleList) { + // 1.2.1 + List<Boolean> contributingChildrenBag = new ArrayList<>(); + // 1.2.2 + for (Activity child : targetActivity.getChildren()) { + // 1.2.2.1 + if (child.getSequencingDefinition().getDeliveryControls().isTracked()) { + // 1.2.2.1.1 + // Make sure this child contributes to the status of its parent. + RollupBehaviorResult rollupBehaviorResult = checkChildForRollup( + new RollupRequest(rollupRequest.getTargetActivityTree(), child) + .setRollupAction(rule.getRollupAction().getValue())); + // 1.2.2.1.2 + if (rollupBehaviorResult.isChildIsIncludedInRollup()) { + // 1.2.2.1.2.1 + // Evaluate the rollup conditions on the child activity. + RollupBehaviorResult evaluateResult = evaluateRollupConditions( + new RollupRequest(rollupRequest.getTargetActivityTree(), child) + .setRollupRule(rule)); + // 1.2.2.1.2.2 + // Account for a possible "unknown" condition evaluation. + // 1.2.2.1.2.2.1 + // 1.2.2.1.2.3 + // 1.2.2.1.2.3.1 + // 1.2.2.1.2.3.1.1 + // 1.2.2.1.2.3.2 + // 1.2.2.1.2.3.2.1 + contributingChildrenBag.add(evaluateResult.getEvaluation()); + } + } + } + // 1.2.3 + // Determine if the appropriate children contributed to rollup; if they did, the status of the + // activity should be changed. + boolean statusChange = false; + // 1.2.4 + // noinspection StatementWithEmptyBody + if (contributingChildrenBag.isEmpty()) { + // 1.2.4.1 + // No action; + // do not change status unless some child contributed to rollup + } else if (rule.getChildActivitySet().getValue().equals("All")) { // 1.2.5 + // 1.2.5.1 + if (!contributingChildrenBag.contains(false) && !contributingChildrenBag.contains(null)) { + // 1.2.5.1.1 + statusChange = true; + } + } else if (rule.getChildActivitySet().getValue().equals("Any")) { // 1.2.6 + // 1.2.6.1 + if (contributingChildrenBag.contains(true)) { + // 1.2.6.1.1 + statusChange = true; + } + } else if (rule.getChildActivitySet().getValue().equals("None")) { // 1.2.7 + // 1.2.7.1 + if (!contributingChildrenBag.contains(true) && !contributingChildrenBag.contains(null)) { + // 1.2.7.1.1 + statusChange = true; + } + } else if (rule.getChildActivitySet().getValue().equals("At Least Count")) { // 1.2.8 + // 1.2.8.1 + if (contributingChildrenBag.stream().filter(c -> c != null && c).count() + >= rule.getRollupMinimumCount().getValue()) { + // 1.2.8.1.1 + statusChange = true; + } + } else if (rule.getChildActivitySet().getValue().equals("At Least Percent")) { // 1.2.9 + // 1.2.9.1 + if (BigDecimal.valueOf(contributingChildrenBag.stream().filter(c -> c != null && c).count() + / (long) contributingChildrenBag.size()).setScale(4, BigDecimal.ROUND_HALF_UP) + .compareTo(rule.getRollupMinimumPercent().getValue()) >= 0) { + // 1.2.9.1.1 + statusChange = true; + } + } + // 1.2.10 + if (statusChange) { + // 1.2.10.1 + // Stop at the first rule that evaluates to true - perform the associated action. + return new RollupBehaviorResult().setEvaluation(true); + } + } + } + // 2 + // No rules evaluated to true - do not perform any action. + return new RollupBehaviorResult().setEvaluation(false); + } + + /** + * Evaluate Rollup Conditions Subprocess [RB.1.4.1] + * + * For an activity, a Condition Combination, and a set of Rollup Conditions; returns True if the condition(s) + * evaluate to True, False if the condition(s) evaluate to False, and Unknown if the condition(s) cannot be evaluated. + * + * Reference: + * Condition Combination SM.5 + * Rollup Condition SM.5 + * Rollup Condition Operator SM.5 + * Tracking Model TM + */ + public static RollupBehaviorResult evaluateRollupConditions(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + RollupRuleDescription rollupRule = rollupRequest.getRollupRule(); + // 1 + // This is used to keep track of the evaluation of the rule's conditions. + List<Boolean> rollupConditionBag = new ArrayList<>(); + // 2 + for (RollupCondition rollupCondition : rollupRule.getRollupConditions()) { + // 2.1 + // Evaluate each condition against the activity's tracking information. + // This evaluation may result in "unknown". + Boolean evaluateResult = evaluateRuleCondition(rollupCondition, targetActivity); + // 2.2 + // Negating "unknown" results in "unknown" + if (rollupCondition.getOperator().getValue().equals("Not")) { + // 2.2.1 + evaluateResult = BooleanUtils.negate(evaluateResult); + } + // 2.3 + // Add the evaluation of this condition to the set of evaluated conditions. + rollupConditionBag.add(evaluateResult); + } + // 3 + // If there are no defined conditions for the rule, we cannot determine if the rule applies. + if (rollupConditionBag.isEmpty()) { + // 3.1 + return new RollupBehaviorResult().setEvaluation(null); + } + // 4 + // 'And' or 'Or' the set of evaluated conditions, based on the rollup rule definition + Boolean evaluation; + if ("All".equals(rollupRule.getConditionCombination().getValue())) { + evaluation = BooleanUtils.and(rollupConditionBag.toArray(new Boolean[0])); + } else { + evaluation = BooleanUtils.or(rollupConditionBag.toArray(new Boolean[0])); + } + return new RollupBehaviorResult().setEvaluation(evaluation); + } + + private static Boolean evaluateRuleCondition(RollupCondition rollupCondition, Activity targetActivity) { + ObjectiveDescription objectiveDescription = targetActivity.findAssociatedObjectiveForContributingToRollup(); + if (objectiveDescription == null) { + return null; + } + ObjectiveProgressInformation objectiveProgressInformation = objectiveDescription + .getObjectiveProgressInformation(); + AttemptProgressInformation attemptProgressInformation = targetActivity.getAttemptProgressInformation(); + ActivityProgressInformation activityProgressInformation = targetActivity.getActivityProgressInformation(); + LimitConditions limitConditions = targetActivity.getSequencingDefinition().getLimitConditions(); + switch (rollupCondition.getRollupCondition().getValue()) { + case "Never": + return false; + case "Satisfied": + return objectiveProgressInformation.isObjectiveProgressStatus() ? + objectiveProgressInformation.isObjectiveSatisfiedStatus() : null; + case "Objective Status Known": + return objectiveProgressInformation.isObjectiveProgressStatus(); + case "Objective Measure Known": + return objectiveProgressInformation.isObjectiveMeasureStatus(); + case "Completed": + return objectiveProgressInformation.isObjectiveCompletionProgressStatus() ? + objectiveProgressInformation.isObjectiveCompletionStatus() : null; +// return attemptProgressInformation.isAttemptProgressStatus() +// && attemptProgressInformation.isAttemptCompletionStatus(); + case "Activity Progress Known": + return objectiveProgressInformation.isObjectiveCompletionProgressStatus(); +// return attemptProgressInformation.isAttemptProgressStatus(); + case "Attempted": + return activityProgressInformation.getActivityAttemptCount().getValue() > 0; + case "Attempt Limit Exceeded": + return activityProgressInformation.isActivityProgressStatus() && limitConditions.isAttemptControl() + && activityProgressInformation.getActivityAttemptCount().getValue() >= limitConditions.getAttemptLimit().getValue(); + } + return null; + } + + + /** + * Check Child for Rollup Subprocess [RB.1.4.2] + * + * For an activity and a Rollup Action; returns True if the activity is included in rollup. + * + * Reference: + * Rollup Action SM.5 + * Rollup Objective Satisfied SM.8 + * RollupProgress Completion SM.8 + * Activity Attempt Count TM.1.2.1 + * Sequencing Rules Check Process UP.2 + * adlseq:requiredForSatisfied SCORM SN + * adlseq:requiredForNotSatisfied SCORM SN + * adlseq:requiredForCompleted SCORM SN + * adlseq:requiredForIncomplete SCORM SN + * + * @see UtilityProcess#processSequencingRulesCheck(UtilityProcessRequest) UP.2 + */ + public static RollupBehaviorResult checkChildForRollup(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + String rollupAction = rollupRequest.getRollupAction(); + // 1 + boolean included = false; + // 2 + if ("Satisfied".equals(rollupAction) || "Not Satisfied".equals(rollupAction)) { + // 2.1 + // Test the objective rollup control. + if (targetActivity.getSequencingDefinition().getRollupControls().isRollupObjectiveSatisfied()) { + // 2.1.1 + // Default Behavior - aldseq:requiredFor[xxx] == always. + included = true; + // 2.1.2 + if (("Satisfied".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForSatisfied().getValue().equals("ifNotSuspended")) + || ("Not Satisfied".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForNotSatisfied().getValue().equals("ifNotSuspended"))) { + // 2.1.2.1 + if (!targetActivity.getActivityProgressInformation().isActivityProgressStatus() + || (targetActivity.getActivityProgressInformation().getActivityAttemptCount().getValue() > 0 + && targetActivity.getActivityStateInformation().isActivityIsSuspended())) { + // 2.1.2.1.1 + included = false; + } + } else { // 2.1.3 + // 2.1.3.1 + if (("Satisfied".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForSatisfied().getValue().equals("ifAttempted")) + || ("Not Satisfied".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForNotSatisfied().getValue().equals("ifAttempted"))) { + // 2.1.3.1.1 + if (!targetActivity.getActivityProgressInformation().isActivityProgressStatus() + || targetActivity.getActivityProgressInformation().getActivityAttemptCount().getValue() == 0) { + // 2.1.3.1.1.1 + included = false; + } + } else { // 2.1.3.2 + // 2.1.3.2.1 + if (("Satisfied".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForSatisfied().getValue().equals("ifNotSkipped")) + || ("Not Satisfied".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForNotSatisfied().getValue().equals("ifNotSkipped"))) { + // 2.1.3.2.1.1 + UtilityProcessResult utilityProcessResult = UtilityProcess.processSequencingRulesCheck( + new UtilityProcessRequest(rollupRequest.getTargetActivityTree(), targetActivity) + .setConditionType(ConditionType.PRECONDITION).setRuleActions("Skip")); + // 2.1.3.2.1.2 + if (utilityProcessResult.getAction() != null) { + included = false; + } + } + } + } + } + } + // 3 + if ("Completed".equals(rollupAction) || "Incomplete".equals(rollupAction)) { + // 3.1 + // Test the progress rollup control. + if (targetActivity.getSequencingDefinition().getRollupControls().isRollupProgressCompletion()) { + // 3.1.1 + // Default Behavior - adlseq:requiredFor[xxx] == always. + included = true; + // 3.1.2 + if (("Completed".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForCompleted().getValue().equals("ifNotSuspended")) + || ("Incomplete".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForIncomplete().getValue().equals("ifNotSuspended"))) { + // 3.1.2.1 + if (!targetActivity.getActivityProgressInformation().isActivityProgressStatus() + || (targetActivity.getActivityProgressInformation().getActivityAttemptCount().getValue() > 0 + && targetActivity.getActivityStateInformation().isActivityIsSuspended())) { + // 3.1.2.1.1 + included = false; + } + } else { // 3.1.3 + // 3.1.3.1 + if (("Completed".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForCompleted().getValue().equals("ifAttempted")) + || ("Incomplete".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForIncomplete().getValue().equals("ifAttempted"))) { + // 3.1.3.1.1 + if (!targetActivity.getActivityProgressInformation().isActivityProgressStatus() + || targetActivity.getActivityProgressInformation().getActivityAttemptCount().getValue() == 0) { + // 3.1.3.1.1.1 + included = false; + } + } else { // 3.1.3.2 + // 3.1.3.2.1 + if (("Completed".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForCompleted().getValue().equals("ifNotSkipped")) + || ("Incomplete".equals(rollupAction) && targetActivity.getSequencingDefinition() + .getRollupConsiderationControls().getRequiredForIncomplete().getValue().equals("ifNotSkipped"))) { + // 3.1.3.2.1.1 + UtilityProcessResult utilityProcessResult = UtilityProcess.processSequencingRulesCheck( + new UtilityProcessRequest(rollupRequest.getTargetActivityTree(), targetActivity) + .setConditionType(ConditionType.PRECONDITION).setRuleActions("Skip")); + // 3.1.3.2.1.2 + if (utilityProcessResult.getAction() != null) { + included = false; + } + } + } + } + } + } + return new RollupBehaviorResult().setChildIsIncludedInRollup(included); + } + + /** + * Overall Rollup Process [RB.1.5] + * + * For an activity; may change the tracking information for the activity and its ancestor. + * + * Reference: + * Activity Progress Rollup Process RB.1.3 + * Measure Rollup Process RB.1.1a + * Completion Measure Rollup Process RB.1.1b + * Objective Rollup Process RB.1.2 + * Tracked SM.11 + * Tracking Model TM + * + * @see RollupBehavior#processMeasureRollup(RollupRequest) RB.1.1a + * @see RollupBehavior#processCompletionMeasureRollup(RollupRequest) RB.1.1b + * @see RollupBehavior#processObjectiveRollupUsingMeasureProcess(RollupRequest) RB.1.2a + * @see RollupBehavior#processObjectiveRollupUsingRules(RollupRequest) RB.1.2b + * @see RollupBehavior#processActivityProgressRollupUsingMeasure(RollupRequest) RB.1.3a + * @see RollupBehavior#processActivityProgressRollupUsingRules(RollupRequest) RB.1.3b + */ + public static void overallRollup(RollupRequest rollupRequest) { + Activity targetActivity = rollupRequest.getTargetActivity(); + // 1 + // From the root of the activity tree to the activity, inclusive, in reverse order + List<Activity> activityPath = new LinkedList<>(); + Activity tmp = targetActivity; + while (tmp != null) { + activityPath.add(tmp); + tmp = tmp.getParentActivity(); + } + // 2 + if (activityPath.isEmpty()) { + // 2.1 + // Nothing to Rollup. + return; + } + // 3 + for (Activity activity : activityPath) { + // 3.1 + // Only apply Measure Rollup to non-leaf activities. + if (!activity.getChildren().isEmpty()) { + // 3.1.1 + // Rollup the activity's measure + processMeasureRollup(new RollupRequest(rollupRequest.getTargetActivityTree(), activity)); + // 3.1.2 + // Rollup the activity's progress measure + processCompletionMeasureRollup(new RollupRequest(rollupRequest.getTargetActivityTree(), activity)); + } + // 3.2 + // Apply the appropriate Objective Rollup Process to the activity + processObjectiveRollupUsingMeasureProcess( + new RollupRequest(rollupRequest.getTargetActivityTree(), activity)); + processObjectiveRollupUsingRules( + new RollupRequest(rollupRequest.getTargetActivityTree(), activity)); + // 3.3 + // Apply the appropriate Activity Progress Rollup Process to the activity + processActivityProgressRollupUsingMeasure( + new RollupRequest(rollupRequest.getTargetActivityTree(), activity)); + processActivityProgressRollupUsingRules( + new RollupRequest(rollupRequest.getTargetActivityTree(), activity)); + } + + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/SelectionAndRandomizationBehavior.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/SelectionAndRandomizationBehavior.java new file mode 100644 index 00000000..bd4f8b4c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/SelectionAndRandomizationBehavior.java @@ -0,0 +1,135 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.xboe.module.scorm.sn.api.request.SelectionAndRandomizationRequest; +import com.xboe.module.scorm.sn.model.definition.RandomizationControls; +import com.xboe.module.scorm.sn.model.definition.SelectionControls; +import com.xboe.module.scorm.sn.model.tree.Activity; + +public class SelectionAndRandomizationBehavior { + + /** + * Select Children Process [SR.1] + * + * For an activity; may change the Available Children for the activity. + * + * Reference: + * Activity is Active AM.1.1 + * Activity is Suspended AM.1.1 + * Available Children AM.1.1 + * Activity Progress Status TM.1.2.1 + * Selection Count SM.9 + * Selection Count Status SM.9 + * Selection Timing SM.9 + */ + public static void selectChildren(SelectionAndRandomizationRequest request) { + Activity targetActivity = request.getTargetActivity(); + SelectionControls selectionControls = targetActivity.getSequencingDefinition().getSelectionControls(); + // 1 + // Cannot apply selection to a leaf activity. + if (targetActivity.isLeaf()) { + // 1.1 + return; + } + // 2 + // Cannot apply selection to a suspended or active activity. + if (targetActivity.getActivityStateInformation().isActivityIsSuspended() + || targetActivity.getActivityStateInformation().isActivityIsActive()) { + // 2.1 + return; + } + // 3 + if (selectionControls.getSelectionTiming().getValue().equals("Once")) { // 4 + // 4.1 + // If the activity has not been attempted yet. + if (!targetActivity.getActivityProgressInformation().isActivityProgressStatus()) { + // 4.1.1 + if (selectionControls.isSelectionCountStatus()) { + // 4.1.1.1 + List<Activity> childList = new LinkedList<>(); + // 4.1.1.2 + Set<Integer> indexSet = new HashSet<>(); + int times = Math.min( + selectionControls.getSelectionCount().getValue(), targetActivity.getChildren().size()); + while (indexSet.size() < times) { + indexSet.add((int) (Math.random() * (targetActivity.getChildren().size() - 1))); + } + List<Integer> indexList = new ArrayList<>(indexSet); + Collections.sort(indexList); + for (Integer i : indexList) { + childList.add(targetActivity.getChildren().get(i)); + } + // 4.1.1.3 + targetActivity.getActivityStateInformation().getAvailableChildren().clear(); + targetActivity.getActivityStateInformation().getAvailableChildren().addAll(childList); + } + } + // 4.2 + // exit + } + } + + /** + * Randomize Children Process [SR.2] + * + * For an activity; may change the Available Children for the activity. + * + * Reference: + * Activity is Active AM.1.1 + * Activity is Suspended AM.1.1 + * Available Children AM.1.1 + * Activity Progress Status TM.1.2.1 + * Randomize Children SM.10 + * Randomization Timing SM.10 + */ + public static void randomizeChildren(SelectionAndRandomizationRequest request) { + Activity targetActivity = request.getTargetActivity(); + RandomizationControls randomizationControls = targetActivity.getSequencingDefinition().getRandomizationControls(); + // 1 + // Cannot apply randomization to a leaf activity + if (targetActivity.isLeaf()) { + // 1.1 + return; + } + // 2 + // Cannot apply randomization to a suspended or active activity. + if (targetActivity.getActivityStateInformation().isActivityIsSuspended() + || targetActivity.getActivityStateInformation().isActivityIsActive()) { + // 2.1 + return; + } + // 3 + // 4 + if (randomizationControls.getRandomizationTiming().getValue().equals("Once")) { + // 4.1 + // If the activity has not been attempted yet. + if (!targetActivity.getActivityProgressInformation().isActivityProgressStatus()) { + // 4.1.1 + if (randomizationControls.isRandomizeChildren()) { + // 4.1.1.1 + Collections.shuffle(targetActivity.getActivityStateInformation().getAvailableChildren()); + } + } + // 4.2 + // exit + } else if (randomizationControls.getRandomizationTiming().getValue().equals("On Each New Attempt")) { // 5 + // 5.1 + if (randomizationControls.isRandomizeChildren()) { + // 5.1.1 + Collections.shuffle(targetActivity.getActivityStateInformation().getAvailableChildren()); + } + // 5.2 + // exit + } + // 6 + // Undefined timing attribute + // exit + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/SequencingBehavior.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/SequencingBehavior.java new file mode 100644 index 00000000..cf0f09a5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/SequencingBehavior.java @@ -0,0 +1,1363 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import com.xboe.module.scorm.sn.api.behavior.common.TraversalDirection; +import com.xboe.module.scorm.sn.api.behavior.result.SequencingBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.SequencingException; +import com.xboe.module.scorm.sn.api.behavior.result.UtilityProcessResult; +import com.xboe.module.scorm.sn.api.request.DeliveryRequest; +import com.xboe.module.scorm.sn.api.request.SequencingRequest; +import com.xboe.module.scorm.sn.api.request.SequencingRequest.Type; +import com.xboe.module.scorm.sn.api.request.UtilityProcessRequest; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription.ConditionType; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class SequencingBehavior { + + /** + * Flow Tree Traversal Subprocess [SB.2.1] + * + * For an activity, a traversal direction, a consider children flag, and a previous traversal direction; + * returns the 'next' activity in directed traversal of the activity tree, may return the traversal direction, + * may indicate control be returned to the LTS; and may return an exception code: + * + * Reference: + * Available Children AM.1.1 + * Sequencing Control Forward Only SM.1 + * Terminate Descendent Attempts Process UP.3 + * + * @see UtilityProcess#processTerminateDescendentAttempts(UtilityProcessRequest) UP.3\ + */ + public static SequencingBehaviorResult processFlowTreeTraversal(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity targetActivity = sequencingRequest.getTargetActivity(); + TraversalDirection previousTraversalDirection = sequencingRequest.getPreviousTraversalDirection(); + TraversalDirection traversalDirection = sequencingRequest.getTraversalDirection(); + boolean considerChildren = sequencingRequest.isConsiderChildren(); + // 1 + boolean reversedDirection = false; + // 2 + // Test if we have skipped all of the children in a forward only cluster moving backward. + if (previousTraversalDirection == TraversalDirection.Backward + && targetActivity.getParentActivity().getActivityStateInformation().getAvailableChildren() + .lastIndexOf(targetActivity) == targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().size() - 1) { + // 2.1 + // traversal direction is Backward + traversalDirection = TraversalDirection.Backward; + // 2.2 + // activity is the first activity in the activity's parent's list of Available Children +// targetActivity = targetActivity.getParentActivity().getActivityStateInformation() +// .getAvailableChildren().get(0); + // 2.3 + reversedDirection = true; + } + // 3 + if (traversalDirection == TraversalDirection.Forward) { + // 3.1 + // Walking off the tree causes the sequencing session to end + List<Activity> preOrderActivityList = activityTree.preorder(); + Activity lastAvailableActivity = null; + for (int i = preOrderActivityList.size() - 1; i >= 0; i--) { + if (activityTree.isAvailable(preOrderActivityList.get(i))) { + lastAvailableActivity = preOrderActivityList.get(i); + break; + } + } + if (Objects.equals(targetActivity, lastAvailableActivity) + || (activityTree.isRoot(targetActivity) && !considerChildren)) { + // 3.1.1 + UtilityProcess.processTerminateDescendentAttempts( + new UtilityProcessRequest(activityTree, activityTree.getRoot())); + // 3.1.2 + return new SequencingBehaviorResult().setEndSequencingSession(true); + } + // 3.2 + if (targetActivity.isLeaf() || !considerChildren) { + // 3.2.1 + if (targetActivity.getParentActivity().getActivityStateInformation().getAvailableChildren() + .lastIndexOf(targetActivity) == targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().size() - 1) { + // 3.2.1.1 + // Recursion - Move to the activity's parent's next forward sibling. + // 3.2.1.2 + // Return the result of the recursion + return processFlowTreeTraversal( + new SequencingRequest(sequencingRequest.getRequestType(), activityTree, + targetActivity.getParentActivity()) + .setTraversalDirection(TraversalDirection.Forward) + .setPreviousTraversalDirection(null) + .setConsiderChildren(false)); + } else { // 3.2.2 + // 3.2.2.1 + Activity nextActivity = targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().get(targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().indexOf(targetActivity) + 1); + // 3.2.2.2 + return new SequencingBehaviorResult() + .setNextActivity(nextActivity) + .setTraversalDirection(traversalDirection); + } + } else { // 3.3 Entering a cluster - Forward. + // 3.3.1 + if (!targetActivity.getActivityStateInformation().getAvailableChildren().isEmpty()) { + // 3.3.1.1 + return new SequencingBehaviorResult() + .setNextActivity(targetActivity.getActivityStateInformation().getAvailableChildren().get(0)) + .setTraversalDirection(traversalDirection); + } else { // 3.3.2 + // 3.3.2.1 + return new SequencingBehaviorResult().setException(SequencingException.SB212); + } + } + } + // 4 + if (traversalDirection == TraversalDirection.Backward) { + // 4.1 + // Cannot walk off the root of the activity tree. + if (activityTree.isRoot(targetActivity)) { + // 4.1.1. + return new SequencingBehaviorResult().setException(SequencingException.SB213); + } + // 4.2 + if (targetActivity.isLeaf() || !considerChildren) { + // 4.2.1 + // Only test 'forward only' if we are not going to leave this forward only cluster. + if (!reversedDirection) { + // 4.2.1.1 + // Test the control mode before traversing. + if (targetActivity.getParentActivity().getSequencingDefinition() + .getSequencingControlMode().isSequencingControlForwardOnly()) { + // 4.2.1.1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB214); + } + } + // 4.2.2 + if (targetActivity.getParentActivity().getActivityStateInformation().getAvailableChildren() + .indexOf(targetActivity) == 0) { + // 4.2.2.1 + // Recursion - Move to the activity's parent's next backward sibling. + // 4.2.2.2 + // Return the result of the recursion. + return processFlowTreeTraversal( + new SequencingRequest(sequencingRequest.getRequestType(), activityTree, + targetActivity.getParentActivity()) + .setTraversalDirection(TraversalDirection.Backward) + .setPreviousTraversalDirection(null) + .setConsiderChildren(false)); + } else { // 4.2.3 + // 4.2.3.1 + Activity nextActivity = targetActivity.getActivityStateInformation().getAvailableChildren().get( + targetActivity.getActivityStateInformation().getAvailableChildren() + .indexOf(targetActivity) - 1); + // 4.2.3.2 + return new SequencingBehaviorResult() + .setNextActivity(nextActivity) + .setTraversalDirection(traversalDirection); + } + } else { // 4.3 Entering a cluster - Backward + // 4.3.1 + if (!targetActivity.getActivityStateInformation().getAvailableChildren().isEmpty()) { + // 4.3.1.1 + if (targetActivity.getSequencingDefinition().getSequencingControlMode().isSequencingControlForwardOnly()) { + // 4.3.1.1.1 + // Start at the beginning of a forward only cluster. + return new SequencingBehaviorResult() + .setNextActivity(targetActivity.getActivityStateInformation().getAvailableChildren().get(0)) + .setTraversalDirection(TraversalDirection.Forward); + } else { // 4.3.1.2 + // 4.3.1.2.1 + // Start at the end of the cluster if we are backing into it. + return new SequencingBehaviorResult() + .setNextActivity(targetActivity.getActivityStateInformation().getAvailableChildren() + .get(targetActivity.getActivityStateInformation().getAvailableChildren().size() - 1)); + } + } else { // 4.3.2 + return new SequencingBehaviorResult().setException(SequencingException.SB212); + } + } + } + return new SequencingBehaviorResult(); + } + + /** + * Flow Activity Traversal Subprocess [SB.2.2] + * + * For an activity, a traversal direction, and a previous traversal direction; returns the 'next' activity + * in a directed traversal of the activity tree and True if the activity can be delivered; may indicate + * control be returned to the LTS; may return an exception code. + * + * Reference: + * Check Activity Process UP.5 + * Flow Activity Traversal Subprocess SB.2.2 + * Flow Tree Traversal Subprocess SB.2.1 + * Sequencing Control Flow SM.1 + * Sequencing Rules Check Process UP.2 + * + * @see SequencingBehavior#processFlowActivityTraversal(SequencingRequest) SB.2.2 + * @see SequencingBehavior#processFlowTreeTraversal(SequencingRequest) SB.2.1 + * @see UtilityProcess#processSequencingRulesCheck(UtilityProcessRequest) UP.2 + */ + public static SequencingBehaviorResult processFlowActivityTraversal(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity targetActivity = sequencingRequest.getTargetActivity(); + TraversalDirection traversalDirection = sequencingRequest.getTraversalDirection(); + TraversalDirection previousTraversalDirection = sequencingRequest.getPreviousTraversalDirection(); + // 1 + // Confirm that 'flow' is enabled. + if (!targetActivity.getParentActivity().getSequencingDefinition() + .getSequencingControlMode().isSequencingControlFlow()) { + // 1.1 + return new SequencingBehaviorResult().setDeliverable(false).setNextActivity(targetActivity) + .setException(SequencingException.SB221); + } + UtilityProcessResult utilityProcessResult = UtilityProcess.processSequencingRulesCheck( + new UtilityProcessRequest(activityTree, targetActivity) + .setConditionType(ConditionType.PRECONDITION) + .setRuleActions("Skip")); + // 3 + // Activity is skipped, try to go to the 'next' activity. + if (utilityProcessResult.getAction() != null) { + // 3.1 + SequencingBehaviorResult sequencingBehaviorResult = processFlowTreeTraversal( + new SequencingRequest(sequencingRequest.getRequestType(), activityTree, targetActivity) + .setTraversalDirection(traversalDirection) + .setPreviousTraversalDirection(previousTraversalDirection) + .setConsiderChildren(false)); + // 3.2 + if (sequencingBehaviorResult.getNextActivity() == null) { + // 3.2.1 + return new SequencingBehaviorResult() + .setDeliverable(false) + .setNextActivity(targetActivity) + .setEndSequencingSession(sequencingBehaviorResult.getEndSequencingSession()) + .setException(sequencingBehaviorResult.getException()); + } else { // 3.3 + // 3.3.1 + // Make sure the recursive call considers the correct direction. + SequencingBehaviorResult result; + if (previousTraversalDirection == TraversalDirection.Backward + && sequencingBehaviorResult.getTraversalDirection() == TraversalDirection.Backward) { + // 3.3.1.1 + // Recursive call - make sure the 'next' activity is OK + result = processFlowActivityTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, sequencingBehaviorResult.getNextActivity()) + .setTraversalDirection(traversalDirection).setPreviousTraversalDirection(null)); + } else { // 3.3.2 + // 3.3.2.1 + // Recursive call - make sure the 'next' activity is OK. + result = processFlowActivityTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, sequencingBehaviorResult.getNextActivity()) + .setTraversalDirection(traversalDirection) + .setPreviousTraversalDirection(previousTraversalDirection)); + } + // 3.3.3 + // possible exit from recursion + return result; + } + } + // 4 + // Make sure the activity is allowed + UtilityProcessResult checkActivityResult = UtilityProcess.processCheckActivity( + new UtilityProcessRequest(activityTree, targetActivity)); + // 5 + if (checkActivityResult.getResult()) { + return new SequencingBehaviorResult().setDeliverable(false).setNextActivity(targetActivity) + .setException(SequencingException.SB222); + } + // 6 + // Cannot deliver a non-leaf activity; enter the cluster looking for a leaf. + if (!targetActivity.isLeaf()) { + // 6.1 + SequencingBehaviorResult flowTreeResult = processFlowTreeTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, targetActivity) + .setTraversalDirection(traversalDirection).setPreviousTraversalDirection(null) + .setConsiderChildren(true)); + // 6.2 + if (flowTreeResult.getNextActivity() == null) { + // 6.2.1 + return new SequencingBehaviorResult().setDeliverable(false).setNextActivity(targetActivity) + .setEndSequencingSession(flowTreeResult.getEndSequencingSession()) + .setException(flowTreeResult.getException()); + } else { // 6.3 + SequencingBehaviorResult result; + // 6.3.1 + // Check if we are flowing backward through a forward only cluster - must move forward instead. + if (traversalDirection == TraversalDirection.Backward + && flowTreeResult.getTraversalDirection() == TraversalDirection.Forward) { + // 6.3.1.1 + // Recursive call - Make sure the identified activity is OK. + result = processFlowActivityTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, flowTreeResult.getNextActivity()) + .setTraversalDirection(TraversalDirection.Forward) + .setPreviousTraversalDirection(TraversalDirection.Backward)); + } else { // 6.3.2 + // 6.3.2.1 + // Recursive call - Make sure the identified activity is OK. + result = processFlowActivityTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, flowTreeResult.getNextActivity()) + .setTraversalDirection(traversalDirection) + .setPreviousTraversalDirection(null)); + } + // 6.3.3 + return result; + } + } + // 7 + // Found a leaf + return new SequencingBehaviorResult().setDeliverable(true).setNextActivity(targetActivity); + } + + /** + * Flow Subprocess [SB.2.3] + * + * For an activity, a traversal direction, and a consider children flag; indicates if the flow was successful + * and at what activity the flow stopped; may indicate control be returned to the LTS; may return an exception code. + * + * Reference: + * Flow Activity Traversal Subprocess SB.2.2 + * Flow Tree Traversal Subprocess SB.2.1 + * + * @see SequencingBehavior#processFlowActivityTraversal(SequencingRequest) SB.2.2 + * @see SequencingBehavior#processFlowTreeTraversal(SequencingRequest) SB.2.1 + */ + public static SequencingBehaviorResult processFlow(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity targetActivity = sequencingRequest.getTargetActivity(); + TraversalDirection traversalDirection = sequencingRequest.getTraversalDirection(); + boolean considerChildren = sequencingRequest.isConsiderChildren(); + // 1 + // The candidate activity is where we start 'flowing' from. + + // 2 + // Attempt to move away from the starting activity, one activity in the specified direction. + SequencingBehaviorResult flowTreeResult = processFlowTreeTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, targetActivity) + .setTraversalDirection(traversalDirection).setPreviousTraversalDirection(null) + .setConsiderChildren(considerChildren)); + // 3 + if (flowTreeResult.getNextActivity() == null) { + // 3.1 + return new SequencingBehaviorResult().setIdentifiedActivity(targetActivity) + .setDeliverable(false).setEndSequencingSession(flowTreeResult.getEndSequencingSession()) + .setException(flowTreeResult.getException()); + } else { // 4 + // 4.1 + targetActivity = flowTreeResult.getNextActivity(); + // 4.2 + // Validate the candidate activity and traverse until a valid leaf is encountered. + SequencingBehaviorResult flowActivityResult = processFlowActivityTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, targetActivity) + .setTraversalDirection(traversalDirection).setPreviousTraversalDirection(null)); + // 4.3 + return new SequencingBehaviorResult() + .setIdentifiedActivity(flowActivityResult.getNextActivity()) + .setDeliverable(flowActivityResult.isDeliverable()) + .setEndSequencingSession(flowActivityResult.getEndSequencingSession()) + .setException(flowActivityResult.getException()); + } + } + + /** + * Choice Activity Traversal Subprocess [SB.2.4] + * + * For an activity and a traversal direction; + * returns True if the activity can be reached; may return an exception code. + * + * Reference: + * Sequencing Control Forward Only SM.1 + * Sequencing Rules Check Process UP.2 + * + * @see UtilityProcess#processSequencingRulesCheck(UtilityProcessRequest) UP.2 + */ + public static SequencingBehaviorResult processChoiceActivityTraversal(SequencingRequest sequencingRequest) { + Activity targetActivity = sequencingRequest.getTargetActivity(); + TraversalDirection traversalDirection = sequencingRequest.getTraversalDirection(); + // 1 + if (traversalDirection == TraversalDirection.Forward) { + // 1.1 + UtilityProcessResult checkResult = UtilityProcess.processSequencingRulesCheck( + new UtilityProcessRequest(sequencingRequest.getTargetActivityTree(), targetActivity) + .setConditionType(ConditionType.PRECONDITION) + .setRuleActions("Stop Forward Traversal")); + // 1.2 + if (checkResult.getAction() != null) { + // 1.2.1 + return new SequencingBehaviorResult().setReachable(false).setException(SequencingException.SB241); + } + // 1.3 + return new SequencingBehaviorResult().setReachable(true); + } + // 2 + if (traversalDirection == TraversalDirection.Backward) { + // 2.1 + if (targetActivity.getParentActivity() != null) { + // 2.1.1 + if (targetActivity.getParentActivity().getSequencingDefinition().getSequencingControlMode() + .isSequencingControlForwardOnly()) { + // 2.1.1.1 + return new SequencingBehaviorResult().setReachable(false).setException(SequencingException.SB242); + } + } else { // 2.2 + // 2.2.1 + // Cannot walk backward from the root of the activity tree. + return new SequencingBehaviorResult().setReachable(false).setException(SequencingException.SB243); + } + // 2.3 + return new SequencingBehaviorResult().setReachable(true); + } + return new SequencingBehaviorResult().setReachable(true); + } + + /** + * Start Sequencing Request Process [SB.2.5] + * + * May return a delivery request; may indicate control be returned to the LTS; may return an exception code. + * + * Reference: + * Current Activity AM.1.2 + * Flow Subprocess SB.2.3 + * + * @see SequencingBehavior#processFlow(SequencingRequest) SB.2.3 + */ + public static SequencingBehaviorResult processStartSequencingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // Make sure the sequencing session has not already begun. + if (currentActivity != null) { + // 1.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB251); + } + // 2 + // Before starting, make sure the activity tree contains more than one activity. + if (activityTree.getRoot().isLeaf()) { + // 2.1 + // Only one activity it must be a leaf + return new SequencingBehaviorResult().setDeliveryRequest( + new DeliveryRequest(activityTree, activityTree.getRoot())); + } else { // 3 + // 3.1 + // Attempt to flow into the activity tree. + SequencingBehaviorResult flowResult = processFlow(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, activityTree.getRoot()) + .setTraversalDirection(TraversalDirection.Forward).setConsiderChildren(true)); + // 3.2 + if (!flowResult.isDeliverable()) { + // 3.2.1 + // Nothing to Deliver + return new SequencingBehaviorResult() + .setDeliveryRequest(new DeliveryRequest(activityTree, null)) + .setEndSequencingSession(flowResult.getEndSequencingSession()) + .setException(flowResult.getException()); + } else { // 3.3 + // 3.3.1 + return new SequencingBehaviorResult() + .setDeliveryRequest(new DeliveryRequest(activityTree, flowResult.getIdentifiedActivity())); + } + } + } + + /** + * Resume All Sequencing Request Process [SB.2.6] + * + * May return a delivery request; may return an exception code. + * + * Reference: + * Current Activity AM.1.2 + * Suspended Activity AM.1.2 + */ + public static SequencingBehaviorResult processResumeAllSequencingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + Activity suspendedActivity = activityTree.getGlobalStateInformation().getSuspendedActivity(); + // 1 + // Make sure the sequencing session has not already begun. + if (currentActivity != null) { + // 1.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB261); + } + // 2 + // Make sure there is something to resume. + if (suspendedActivity == null) { + // 2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB262); + } + return new SequencingBehaviorResult() + .setDeliveryRequest(new DeliveryRequest(activityTree, suspendedActivity)); + } + + /** + * Continue Sequencing Request Process [SB.2.7] + * + * May return a delivery request; may indicate control be returned to the LTS; may return an exception code. + * + * Reference: + * Current Activity AM.1.2 + * Flow Subprocess SB.2.3 + * + * @see SequencingBehavior#processFlow(SequencingRequest) SB.2.3 + */ + public static SequencingBehaviorResult processContinueSequencingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // Make sure the sequencing session has already begun. + if (currentActivity == null) { + // 1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB271); + } + // 2 + if (!activityTree.isRoot(currentActivity)) { + // 2.1 + // Confirm a 'flow' traversal is allowed from the activity. + if (!currentActivity.getSequencingDefinition().getSequencingControlMode().isSequencingControlFlow()) { + // 2.1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB272); + } + } + // 3 + // Flow in a forward direction to the next allowed activity. + SequencingBehaviorResult flowResult = processFlow(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, currentActivity) + .setTraversalDirection(TraversalDirection.Forward).setConsiderChildren(false)); + // 4 + if (!flowResult.isDeliverable()) { + // 4.1 + // Nothing to deliver. + return new SequencingBehaviorResult() + .setEndSequencingSession(flowResult.getEndSequencingSession()) + .setException(flowResult.getException()); + } else { // 5 + // 5.1 + return new SequencingBehaviorResult() + .setDeliveryRequest(new DeliveryRequest(activityTree, flowResult.getIdentifiedActivity())); + } + } + + /** + * Previous Sequencing Request Process [SB.2.8] + * + * May return a delivery request; may return an exception code. + * + * Reference: + * Current Activity AM.1.2 + * Flow Subprocess SB.2.3 + * + * @see SequencingBehavior#processFlow(SequencingRequest) SB.2.3 + */ + public static SequencingBehaviorResult processPreviousSequencingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // Make sure the sequencing session has already begun. + if (currentActivity == null) { + // 1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB281); + } + // 2 + if (!activityTree.isRoot(currentActivity)) { + // 2.1 + // Confirm a 'flow' traversal is allowed from the activity. + if (!currentActivity.getSequencingDefinition().getSequencingControlMode().isSequencingControlFlow()) { + // 2.1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB282); + } + } + // 3 + // Flow in a backward direction to the next allowed activity. + SequencingBehaviorResult flowResult = processFlow(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, currentActivity) + .setTraversalDirection(TraversalDirection.Backward).setConsiderChildren(false)); + // 4 + if (!flowResult.isDeliverable()) { + // 4.1 + // Nothing to deliver. + return new SequencingBehaviorResult() + .setEndSequencingSession(flowResult.getEndSequencingSession()) + .setException(flowResult.getException()); + } else { // 5 + // 5.1 + return new SequencingBehaviorResult() + .setDeliveryRequest(new DeliveryRequest(activityTree, flowResult.getIdentifiedActivity())); + } + } + + /** + * Choice Sequencing Request Process [SB.2.9] + * + * For a target activity; may return a delivery request; may change the Current Activity; + * may return an exception code. + * + * Reference: + * Activity is Active AM.1.1 + * Activity is Suspended AM.1.1 + * Available Children AM.1.1 + * Check Activity Process UP.5 + * Choice Flow Subprocess SB.2.9.1 + * Choice Activity Traversal Subprocess SB.2.4 + * Current Activity AM.1.2 + * End Attempt Process UP.4 + * Flow Subprocess SB.2.3 + * Sequencing Control Mode Choice SM.1 + * Sequencing Control Choice Exit SM.1 + * Sequencing Rules Check Process UP.2 + * Terminate Descendent Attempts Process UP.3 + * adlseq:constrainedChoice SCORM SN + * adlseq:preventActivation SCORM SN + * + * @see UtilityProcess#processCheckActivity(UtilityProcessRequest) UP.5 + * @see SequencingBehavior#processChoiceFlow(SequencingRequest) SB.2.9.1 + * @see SequencingBehavior#processChoiceActivityTraversal(SequencingRequest) SB.2.4 + * @see UtilityProcess#processEndAttempt(UtilityProcessRequest) UP.4 + * @see SequencingBehavior#processFlow(SequencingRequest) SB.2.3 + * @see UtilityProcess#processSequencingRulesCheck(UtilityProcessRequest) UP.2 + * @see UtilityProcess#processTerminateDescendentAttempts(UtilityProcessRequest) UP.3 + */ + public static SequencingBehaviorResult processChoiceSequencingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + Activity targetActivity = sequencingRequest.getTargetActivity(); + // 1 + // There must be a target activity for choice. + if (targetActivity == null) { + // 1.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB291); + } + // 2 + // From root of the activity tree to the target activity, inclusive + LinkedList<Activity> activityPath = new LinkedList<>(); + Activity tmp = targetActivity; + while (tmp != null) { + activityPath.addFirst(tmp); + tmp = tmp.getParentActivity(); + } + // 3 + for (Activity activity : activityPath) { + // 3.1 + if (activityTree.isRoot(activity)) { + // 3.1.1 + // The activity is currently not available. + if (!targetActivity.getParentActivity().getActivityStateInformation().getAvailableChildren() + .contains(targetActivity)) { + // 3.1.1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB292); + } + } + // 3.2 + // Cannot choose something that is hidden. + UtilityProcessResult rulesCheckResult = UtilityProcess.processSequencingRulesCheck( + new UtilityProcessRequest(activityTree, activity) + .setConditionType(ConditionType.PRECONDITION) + .setRuleActions("Hidden from Choice")); + // 3.3 + if (rulesCheckResult.getAction() != null) { + // 3.3.1 + // Nothing to deliver. + // Cannot choose something that is hidden. + return new SequencingBehaviorResult().setException(SequencingException.SB293); + } + } + // 4 + if (!activityTree.isRoot(targetActivity)) { + // 4.1 + // Confirm that control mode allow 'choice' of the target. + if (!targetActivity.getParentActivity().getSequencingDefinition() + .getSequencingControlMode().isSequencingControlChoice()) { + // 4.1.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB294); + } + } + // 5 + // Has the sequencing session a already begun? + Activity commonAncestor; + if (currentActivity != null) { + // 5.1 + commonAncestor = activityTree.findCommonAncestorFor(currentActivity, targetActivity); + } else { // 6 + // 6.1 + // No, choosing the target will start the sequencing session. + commonAncestor = activityTree.getRoot(); + } + boolean breakAllCases = false; + // 7 + // Case #1 - select the current activity. + if (Objects.equals(currentActivity, targetActivity)) { + // 7.1 + // Nothing to do in this case. + breakAllCases = true; + } + // 8 + // Case #2 - same cluster; move toward the target activity. + if (!breakAllCases && targetActivity.isSiblingActivity(currentActivity)) { + // 8.1 + // We are attempting to walk toward the target activity. + // Once we reach the target activity, we don't need to test it. + // From the current activity to the target activity, exclusive of the target activity + List<Activity> activityList = ActivityTree.getActivitySequenceList( + currentActivity, true, targetActivity, false); + // 8.2 + // Nothing to choose. + if (activityList.isEmpty()) { + // 8.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB295); + } + // 8.3 + List<Activity> preOrderList = activityTree.preorder(); + TraversalDirection traverse; + if (preOrderList.indexOf(targetActivity) > preOrderList.indexOf(currentActivity)) { + // 8.3.1 + traverse = TraversalDirection.Forward; + } else { // 8.4 + // 8.4.1 + traverse = TraversalDirection.Backward; + } + // 8.5 + for (Activity activity : activityList) { + // 8.5.1 + SequencingBehaviorResult choiceActivityResult = processChoiceActivityTraversal( + new SequencingRequest(sequencingRequest.getRequestType(), activityTree, activity) + .setTraversalDirection(traverse)); + // 8.5.2 + if (!choiceActivityResult.isReachable()) { + // 8.5.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(choiceActivityResult.getException()); + } + } + // 8.6 + breakAllCases = true; + } + // 9 + // Case #3 - path to the target is forward in the activity tree + if (!breakAllCases && Objects.equals(currentActivity, commonAncestor) || currentActivity == null) { + // 9.1 + // From the common ancestor to the target activity, exclusive of the target activity. + LinkedList<Activity> activityPath3 = new LinkedList<>(); + tmp = targetActivity.getParentActivity(); + while (tmp != null) { + activityPath3.addFirst(tmp); + if (tmp.equals(commonAncestor)) { + break; + } + tmp = tmp.getParentActivity(); + } + // 9.2 + if (activityPath3.isEmpty()) { + // 9.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB295); + } + // 9.3 + for (Activity activity : activityPath3) { + // 9.3.1 + SequencingBehaviorResult choiceActivityResult = processChoiceActivityTraversal( + new SequencingRequest(sequencingRequest.getRequestType(), activityTree, activity) + .setTraversalDirection(TraversalDirection.Forward)); + // 9.3.2 + if (!choiceActivityResult.isReachable()) { + // 9.3.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(choiceActivityResult.getException()); + } + // 9.3.3 + // If the activity being considered is not already active, + // make sure we are allowed to activate it. + if (!activity.getActivityStateInformation().isActivityIsActive() + && !activity.equals(commonAncestor) + && activity.getSequencingDefinition().getConstrainChoiceControls().isPreventActivation()) { + // 9.3.3.1 + return new SequencingBehaviorResult().setException(SequencingException.SB296); + } + } + // 9.4 + breakAllCases = true; + } + // 10 + // Case #4 - path to the target is backward in the activity tree. + if (!breakAllCases && Objects.equals(targetActivity, commonAncestor)) { + // 10.1 + // From the current activity to the target activity, inclusive + List<Activity> activityPath4 = new ArrayList<>(); + tmp = currentActivity; + while (tmp != null) { + activityPath4.add(tmp); + if (tmp.equals(targetActivity)) { + break; + } + tmp = tmp.getParentActivity(); + } + // 10.2 + if (activityPath4.isEmpty()) { + // 10.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB295); + } + // 10.3 + for (int i = 0; i < activityPath4.size(); i++) { + // 10.3.1 + if (i != activityPath4.size() - 1) { + // 10.3.1.1 + // Make sure an activity that should not exit will exit if the target is delivered. + if (!activityPath4.get(i).getSequencingDefinition().getSequencingControlMode() + .isSequencingControlChoiceExit()) { + // 10.3.1.1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB297); + } + } + } + // 10.4 + breakAllCases = true; + } + // 11 + // Case #5 - target is a descendent activity of the common ancestor. + if (!breakAllCases && commonAncestor.isDescendent(targetActivity)) { + // 11.1 + // From the current activity to the common ancestor, excluding the common ancestor. + List<Activity> activityPath5 = new LinkedList<>(); + tmp = currentActivity; + while (tmp != null && !tmp.equals(commonAncestor)) { + activityPath5.add(currentActivity); + tmp = tmp.getParentActivity(); + } + // 11.2 + if (activityPath5.isEmpty()) { + // 11.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB295); + } + // 11.3 + Activity constrainedActivity = null; + // 11.4 + // Walk up the tree to the common ancestor. + for (Activity activity : activityPath5) { + // 11.4.1 + // Make sure an activity that should not exit will exit if the target is delivered. + if (!activity.getSequencingDefinition().getSequencingControlMode().isSequencingControlChoiceExit()) { + // 11.4.1.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB297); + } + // 11.4.2 + // Find the closest constrained activity to the current activity. + if (constrainedActivity == null) { + // 11.4.2.1 + if (activity.getSequencingDefinition().getConstrainChoiceControls().isConstrainChoice()) { + // 11.4.2.1.1 + constrainedActivity = activity; + } + } + } + // 11.5 + if (constrainedActivity != null) { + TraversalDirection traverse; + // 11.5.1 + List<Activity> preOrderList = activityTree.preorder(); + if (preOrderList.indexOf(targetActivity) > preOrderList.indexOf(constrainedActivity)) { + // 11.5.1.1 + // 'Flow' in a forward direction to see what activity comes next. + traverse = TraversalDirection.Forward; + } else { // 11.5.2 + // 11.5.2.1 + // 'Flow' in a backward direction to see what activity comes next. + traverse = TraversalDirection.Backward; + } + // 11.5.3 + SequencingBehaviorResult choiceFlowResult = processChoiceFlow(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, constrainedActivity) + .setTraversalDirection(traverse)); + // 11.5.4 + Activity activityToConsider = choiceFlowResult.getIdentifiedActivity(); + // 11.5.5 + // Make sure the target activity is within the set of 'flow' constrained choices. + if (!(activityToConsider.isDescendent(targetActivity) && activityTree.isAvailable(targetActivity)) + && !Objects.equals(targetActivity, activityToConsider) + && !Objects.equals(targetActivity, constrainedActivity)) { + // 11.5.5.1 + return new SequencingBehaviorResult().setException(SequencingException.SB298); + } + } + // 11.6 + // from the common ancestor to the target activity, exclusive of the target activity + LinkedList<Activity> activityPath6 = new LinkedList<>(); + tmp = targetActivity.getParentActivity(); + while (tmp != null) { + activityPath6.addFirst(tmp); + tmp = tmp.getParentActivity(); + } + // 11.7 + if (activityPath6.isEmpty()) { + // 11.7.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB295); + } + // 11.8 + // Walk toward the target activity. + List<Activity> preOrderList = activityTree.preorder(); + if (preOrderList.indexOf(targetActivity) > preOrderList.indexOf(currentActivity)) { + // 11.8.1 + for (Activity activity : activityPath6) { + // 11.8.1.1 + SequencingBehaviorResult choiceActivityResult = processChoiceActivityTraversal( + new SequencingRequest(sequencingRequest.getRequestType(), activityTree, activity) + .setTraversalDirection(TraversalDirection.Forward)); + // 11.8.1.2 + if (!choiceActivityResult.isReachable()) { + // 11.8.1.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(choiceActivityResult.getException()); + } + // 11.8.1.3 + // If the activity being considered is not already active, + // make sure we are allowed to activate it. + if (!activity.getActivityStateInformation().isActivityIsActive() + && !Objects.equals(activity, commonAncestor) && activity + .getSequencingDefinition().getConstrainChoiceControls().isPreventActivation()) { + // 11.8.1.3.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB296); + } + } + } else { // 11.9 + // 11.9.1 + activityPath6.addLast(targetActivity); + // 11.9.2 + for (Activity activity : activityPath6) { + // 11.9.2.1 + // If the activity being considered is not already active, + // make sure we are allowed to activate it. + if (!activity.getActivityStateInformation().isActivityIsActive() + && !Objects.equals(activity, commonAncestor) && activity + .getSequencingDefinition().getConstrainChoiceControls().isPreventActivation()) { + // 11.9.2.1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB296); + } + } + } + // 11.10 + breakAllCases = true; + } + // 12 + if (targetActivity.isLeaf()) { + // 12.1 + return new SequencingBehaviorResult() + .setDeliveryRequest(new DeliveryRequest(activityTree, targetActivity)); + } + // 13 + // The identified activity is a cluster. + // Enter the cluster and attempt to find a descendent leaf to deliver. + SequencingBehaviorResult flowResult = processFlow(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, targetActivity) + .setTraversalDirection(TraversalDirection.Forward) + .setConsiderChildren(true)); + // 14 + // Nothing to deliver, but we succeeded in reaching the target activity - move the current activity. + if (!flowResult.isDeliverable()) { + // 14.1 + UtilityProcess.processTerminateDescendentAttempts(new UtilityProcessRequest(activityTree, commonAncestor)); + // 14.2 + UtilityProcess.processEndAttempt(new UtilityProcessRequest(activityTree, commonAncestor)); + // 14.3 + activityTree.getGlobalStateInformation().setCurrentActivity(targetActivity); + // 14.4 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB299); + } else { // 15 + // 15.1 + return new SequencingBehaviorResult() + .setDeliveryRequest(new DeliveryRequest(activityTree, flowResult.getIdentifiedActivity())); + } + } + + /**F + * Choice Flow Subprocess [SB.2.9.1] + * + * For an activity and a traversal direction; indicates at what activity the flow stopped. + * + * Reference: + * Choice Flow Tree Traversal Subprocess SB.2.9.2 + * + * @see SequencingBehavior#processChoiceFlowTreeTraversal(SequencingRequest) SB.2.9.2 + */ + public static SequencingBehaviorResult processChoiceFlow(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity targetActivity = sequencingRequest.getTargetActivity(); + TraversalDirection traversalDirection = sequencingRequest.getTraversalDirection(); + // 1 + // Attempt to move away from the activity, 'one' activity in the specified direction. + SequencingBehaviorResult choiceFlowTreeResult = processChoiceFlowTreeTraversal( + new SequencingRequest(sequencingRequest.getRequestType(), activityTree, targetActivity) + .setTraversalDirection(traversalDirection)); + // 2 + if (choiceFlowTreeResult.getNextActivity() == null) { + // 2.1 + return new SequencingBehaviorResult().setIdentifiedActivity(targetActivity); + } else { // 3 + // 3.1 + return new SequencingBehaviorResult().setIdentifiedActivity(choiceFlowTreeResult.getNextActivity()); + } + } + + /** + * Choice Flow Tree Traversal Subprocess [SB.2.9.2] + * + * For an activity, a traversal direction; returns the 'next' activity in directed traversal of the activity tree. + * + * Reference: + * Available Children AM.1.1 + * Choice Flow Tree Traversal Subprocess SB.2.9.2 + * + * @see SequencingBehavior#processChoiceFlowTreeTraversal(SequencingRequest) SB.2.9.2 + */ + public static SequencingBehaviorResult processChoiceFlowTreeTraversal(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity targetActivity = sequencingRequest.getTargetActivity(); + TraversalDirection traversalDirection = sequencingRequest.getTraversalDirection(); + // 1 + if (traversalDirection == TraversalDirection.Forward) { + // 1.1 + // Cannot walk off the activity tree. + List<Activity> preOrderActivityList = activityTree.preorder(); + Activity lastAvailableActivity = null; + for (int i = preOrderActivityList.size() - 1; i >= 0; i--) { + if (activityTree.isAvailable(preOrderActivityList.get(i))) { + lastAvailableActivity = preOrderActivityList.get(i); + break; + } + } + if (Objects.equals(targetActivity, lastAvailableActivity) || activityTree.isRoot(targetActivity)) { + // 1.1.1 + return new SequencingBehaviorResult(); + } + // 1.2 + if (targetActivity.getParentActivity().getActivityStateInformation().getAvailableChildren() + .lastIndexOf(targetActivity) == targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().size() - 1) { + // 1.2.1 + // Recursion - Move to the activity's parent's next forward sibling. + // 1.2.2 + // Return the result of the recursion + return processChoiceFlowTreeTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, targetActivity.getParentActivity()) + .setTraversalDirection(TraversalDirection.Forward)); + } else { // 1.3 + // 1.3.1 + Activity nextActivity = targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().get(targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().indexOf(targetActivity) + 1); + // 1.3.2 + return new SequencingBehaviorResult().setNextActivity(nextActivity); + } + } + // 2 + if (traversalDirection == TraversalDirection.Backward) { + // 2.1 + // Cannot walk off the root of the activity tree. + if (activityTree.isRoot(targetActivity)) { + // 2.1.1 + return new SequencingBehaviorResult(); + } + // 2.2 + if (targetActivity.getParentActivity().getActivityStateInformation().getAvailableChildren() + .indexOf(targetActivity) == 0) { + // 2.2.1 + // Recursion – Move to the activity’s parent’s next backward sibling. + // 2.2.2 + // // Return the result of the recursion + return processChoiceFlowTreeTraversal(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, targetActivity.getParentActivity()) + .setTraversalDirection(TraversalDirection.Backward)); + } else { // 2.3 + // 2.3.1 + Activity nextActivity = targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().get(targetActivity.getParentActivity().getActivityStateInformation() + .getAvailableChildren().indexOf(targetActivity) - 1); + // 2.3.2 + return new SequencingBehaviorResult().setNextActivity(nextActivity); + } + } + return new SequencingBehaviorResult(); + } + + /** + * Retry Sequencing Request Process [SB.10] + * + * May return a delivery request; may return an exception code. + * + * Reference: + * Activity is Active AM.1.1 + * Activity is Suspended AM.1.1 + * Current Activity AM.1.2 + * Flow Subprocess SB.2.3 + * + * @see SequencingBehavior#processFlow(SequencingRequest) SB.2.3 + */ + public static SequencingBehaviorResult processRetrySeqeuncingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // Make sure the sequencing session has already begun. + if (currentActivity == null) { + // 1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB2101); + } + // 2 + // Cannot retry an activity that is still active or suspended. + if (currentActivity.getActivityStateInformation().isActivityIsActive() + || currentActivity.getActivityStateInformation().isActivityIsSuspended()) { + // 2.1 + return new SequencingBehaviorResult().setException(SequencingException.SB2102); + } + // 3 + if (!currentActivity.isLeaf()) { + // 3.1 + SequencingBehaviorResult flowResult = processFlow(new SequencingRequest( + sequencingRequest.getRequestType(), activityTree, currentActivity) + .setTraversalDirection(TraversalDirection.Forward) + .setConsiderChildren(true)); + // 3.2 + if (!flowResult.isDeliverable()) { + // 3.2.1 + // Nothing to deliver. + return new SequencingBehaviorResult().setException(SequencingException.SB2103); + } else { // 3.3 + // 3.3.1 + return new SequencingBehaviorResult().setDeliveryRequest( + new DeliveryRequest(activityTree, flowResult.getIdentifiedActivity())); + } + } else { // 4 + return new SequencingBehaviorResult().setDeliveryRequest(new DeliveryRequest(activityTree, currentActivity)); + } + } + + /** + * Exit Sequencing Request Process [SB.2.11] + * + * Indicates if the sequencing session has ended; may return an exception code. + * + * Reference: + * Activity is Active AM.1.1 + * Current Activity AM.1.2 + */ + public static SequencingBehaviorResult processExitSequencingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // Make sure the sequencing session has already begun. + if (currentActivity == null) { + // 1.1 + return new SequencingBehaviorResult() + .setEndSequencingSession(false) + .setException(SequencingException.SB2111); + } + // 2 + // Make sure the current activity has already been terminated. + if (currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 2.1 + return new SequencingBehaviorResult() + .setEndSequencingSession(false) + .setException(SequencingException.SB2112); + } + // 3 + if (activityTree.isRoot(currentActivity)) { + // 3.1 + // The sequencing session has ended, return control to the LTS. + return new SequencingBehaviorResult() + .setEndSequencingSession(true); + } + // 4 + return new SequencingBehaviorResult().setEndSequencingSession(false); + } + + /** + * Sequencing Request Process [SB.2.12] + * + * For a sequencing request; validates the sequencing request; my return a delivery request; may indicate + * control be returned to the LTS; may return an exception code. + * + * Reference: + * Choice Sequencing Request Process SB.2.9 + * Continue Sequencing Request Process SB.2.7 + * Exit Sequencing Request Process SB.2.11 + * Previous Sequencing Request Process SB.2.8 + * Resume All Sequencing Request Process SB.2.6 + * Retry Sequencing Request Process SB.2.10 + * Start Sequencing Request Process SB.2.5 + * Jump Sequencing Request Process SB.2.13 + * + * @see SequencingBehavior#processChoiceSequencingRequest(SequencingRequest) SB.2.9 + * @see SequencingBehavior#processContinueSequencingRequest(SequencingRequest) SB.2.7 + * @see SequencingBehavior#processExitSequencingRequest(SequencingRequest) SB.2.11 + * @see SequencingBehavior#processPreviousSequencingRequest(SequencingRequest) SB.2.8 + * @see SequencingBehavior#processResumeAllSequencingRequest(SequencingRequest) SB.2.6 + * @see SequencingBehavior#processRetrySeqeuncingRequest(SequencingRequest) SB.2.10 + * @see SequencingBehavior#processStartSequencingRequest(SequencingRequest) SB.2.5 + * @see SequencingBehavior#processJumpSequencingRequest(SequencingRequest) SB.2.13 + */ + public static SequencingBehaviorResult processSequencingRequest(SequencingRequest sequencingRequest) { + SequencingRequest.Type type = sequencingRequest.getRequestType(); + SequencingBehaviorResult result; + // 1 + if (type == Type.Start) { + // 1.1 + result = processStartSequencingRequest(sequencingRequest); + // 1.2 + if (result.getException() != null) { + // 1.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 1.3 + // 1.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setDeliveryRequest(result.getDeliveryRequest()) + .setEndSequencingSession(result.getEndSequencingSession()); + } + } else if (type == Type.ResumeAll) { // 2 + // 2.1 + result = processResumeAllSequencingRequest(sequencingRequest); + // 2.2 + if (result.getException() != null) { + // 2.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 2.3 + // 2.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setDeliveryRequest(result.getDeliveryRequest()); + } + } else if (type == Type.Exit) { // 3 + // 3.1 + result = processExitSequencingRequest(sequencingRequest); + // 3.2 + if (result.getException() != null) { + // 3.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 3.3 + // 3.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setEndSequencingSession(result.getEndSequencingSession()); + } + } else if (type == Type.Retry) { // 4 + // 4.1 + result = processRetrySeqeuncingRequest(sequencingRequest); + // 4.2 + if (result.getException() != null) { + // 4.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 4.3 + // 4.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setDeliveryRequest(result.getDeliveryRequest()); + } + } else if (type == Type.Continue) { // 5 + // 5.1 + result = processContinueSequencingRequest(sequencingRequest); + // 5.2 + if (result.getException() != null) { + // 5.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 5.3 + // 5.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setDeliveryRequest(result.getDeliveryRequest()) + .setEndSequencingSession(result.getEndSequencingSession()); + } + } else if (type == Type.Previous) { //6 + // 6.1 + result = processPreviousSequencingRequest(sequencingRequest); + // 6.2 + if (result.getException() != null) { + // 6.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 6.3 + // 6.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setDeliveryRequest(result.getDeliveryRequest()); + } + } else if (type == Type.Choice) { // 7 + // 7.1 + result = processChoiceSequencingRequest(sequencingRequest); + // 7.2 + if (result.getException() != null) { + // 7.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 7.3 + // 7.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setDeliveryRequest(result.getDeliveryRequest()); + } + } else if (type == Type.Jump) { // 8 + // 8.1 + result = processJumpSequencingRequest(sequencingRequest); + // 8.2 + if (result.getException() != null) { + // 8.2.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(result.getException()); + } else { // 8.3 + // 8.3.1 + return new SequencingBehaviorResult() + .setValidSequencingRequest(true) + .setDeliveryRequest(result.getDeliveryRequest()); + } + } + // 9 + // Invalid sequencing request. + return new SequencingBehaviorResult() + .setValidSequencingRequest(false) + .setException(SequencingException.SB2121); + } + + /** + * Jump Sequencing Request Process [SB.2.13] + * + * For a target activity; may return a delivery request; may return an exception code. + * + * Reference: Current Activity AM.1.2 + */ + public static SequencingBehaviorResult processJumpSequencingRequest(SequencingRequest sequencingRequest) { + ActivityTree activityTree = sequencingRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // Make sure the sequencing session has not already begun. + if (currentActivity == null) { + // 1.1 + return new SequencingBehaviorResult().setException(SequencingException.SB2131); + } + // 2 + return new SequencingBehaviorResult().setDeliveryRequest( + new DeliveryRequest(activityTree, sequencingRequest.getTargetActivity())); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/TerminationBehavior.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/TerminationBehavior.java new file mode 100644 index 00000000..c2d22bd5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/TerminationBehavior.java @@ -0,0 +1,376 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import com.xboe.module.scorm.sn.api.behavior.result.SequencingException; +import com.xboe.module.scorm.sn.api.behavior.result.TerminationBehaviorResult; +import com.xboe.module.scorm.sn.api.behavior.result.UtilityProcessResult; +import com.xboe.module.scorm.sn.api.request.RollupRequest; +import com.xboe.module.scorm.sn.api.request.SequencingRequest; +import com.xboe.module.scorm.sn.api.request.TerminationRequest; +import com.xboe.module.scorm.sn.api.request.TerminationRequest.Type; +import com.xboe.module.scorm.sn.api.request.UtilityProcessRequest; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription.ConditionType; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class TerminationBehavior { + + /** + * Sequencing Exit Action Rules Subprocess [TB2.1] + * + * For the Current Activity; may change the Current Activity. + * + * Reference: + * Current Activity AM.1.2 + * End Attempt Process UP.4 + * Sequencing Rules Check Process UP.2 + * Sequencing Rule Description SM.2 + * Terminate Descendent Attempts Process UP.3 + * + * @see UtilityProcess#processEndAttempt(UtilityProcessRequest) UP.4 + * @see UtilityProcess#processSequencingRulesCheck(UtilityProcessRequest) UP.2 + * @see UtilityProcess#processTerminateDescendentAttempts(UtilityProcessRequest) UP.3 + */ + public static void processSequencingExitActionRules(TerminationRequest terminationRequest) { + ActivityTree activityTree = terminationRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + + // 1 + // from the root of the activity tree to the parent of the current activity, inclusive + LinkedList<Activity> activityPath = new LinkedList<>(); + Activity tmp = currentActivity.getParentActivity(); + while (tmp != null) { + activityPath.addFirst(tmp); + tmp = tmp.getParentActivity(); + } + // 2 + Activity exitTarget = null; + // 3 + // Evaluate all exit rules along the active path, starting at the root of the activity tree. + for (Activity activity : activityPath) { + // 3.1 + UtilityProcessResult utilityProcessResult = UtilityProcess.processSequencingRulesCheck( + new UtilityProcessRequest(activityTree, activity) + .setConditionType(ConditionType.EXITCONDITION).setRuleActions("Exit")); + // 3.2 + if (utilityProcessResult.getAction() != null) { + // 3.2.1 + // Stop at the first activity that has an exit rule evaluating to true + exitTarget = activity; + // 3.2.2 + break; + } + } + // 4 + if (exitTarget != null) { + // 4.1 + // End the current attempt on all active descendents. + UtilityProcess.processTerminateDescendentAttempts( + new UtilityProcessRequest(terminationRequest.getTargetActivityTree(), exitTarget)); + // 4.2 + // End the current attempt on the 'exiting' activity + UtilityProcess.processEndAttempt( + new UtilityProcessRequest(terminationRequest.getTargetActivityTree(), exitTarget)); + // 4.3 + // Move the current activity to the activity that identified for termination. + activityTree.getGlobalStateInformation().setCurrentActivity(exitTarget); + } + } + + /** + * Sequencing Post Condition Rules Subprocess [TB2.2] + * + * For the Current Activity; may return a termination request and a sequencing request. + * + * Reference: + * Activity is Suspended TM.1.1 + * Current Activity AM.1.2 + * Sequencing Rules Check Process UP.2 + * Sequencing Rule Description SM.2 + * + * @see UtilityProcess#processSequencingRulesCheck(UtilityProcessRequest) UP.2 + */ + public static TerminationBehaviorResult processSequencingPostConditionRules(TerminationRequest terminationRequest) { + ActivityTree activityTree = terminationRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // Do not apply post condition rules to a suspended activity. + if (currentActivity.getActivityStateInformation().isActivityIsSuspended()) { + // 1.1 + return new TerminationBehaviorResult(); + } + // 2 + // Apply the post condition rules to the current activity. + UtilityProcessResult utilityProcessResult = UtilityProcess.processSequencingRulesCheck( + new UtilityProcessRequest(activityTree, currentActivity) + .setConditionType(ConditionType.POSTCONDITION) + .setRuleActions(ConditionType.POSTCONDITION.getRuleActionVocabularyTable())); + // 3 + if (utilityProcessResult.getAction() != null) { + // 3.1 + if (Arrays.asList("Retry", "Continue", "Previous").contains(utilityProcessResult.getAction())) { + // 3.1.1 + // Attempt to override any pending sequencing request with this one. + return new TerminationBehaviorResult() + .setSequencingRequest(new SequencingRequest( + SequencingRequest.Type.valueOf(utilityProcessResult.getAction()), + activityTree, currentActivity)); + } + // 3.2 + if (Arrays.asList("Exit Parent", "Exit All").contains(utilityProcessResult.getAction())) { + // 3.2.1 + // Terminate the appropriate activity(ies). + TerminationRequest.Type type = "Exit Parent".equals(utilityProcessResult.getAction()) ? + Type.ExitParent : Type.ExitAll; + return new TerminationBehaviorResult() + .setTerminationRequest(new TerminationRequest(type, activityTree, currentActivity)); + } + // 3.3 + if ("Retry All".equals(utilityProcessResult.getAction())) { + // 3.3.1 + // Terminate all active activities and move the current activity to the root of the activity tree; + // then perform an "in-process" start. + return new TerminationBehaviorResult() + .setTerminationRequest(new TerminationRequest(Type.ExitAll, activityTree, currentActivity)) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Retry, activityTree, currentActivity)); + } + } + // 4 + return new TerminationBehaviorResult(); + } + + /** + * Termination Request Process [TB.2.3] + * + * For a termination request, ends the current attempt on the Current Activity, returns the validity of the + * termination request; may return a sequencing request; may return an exception code. + * + * Reference: + * Activity is Active AM.1.1 + * Activity is Suspended AM.1.1 + * Current Activity AM.1.2 + * End Attempt Process UP.4 + * Sequencing Exit Action Rules Subprocess TB.2.1 + * Sequencing Post Condition Rules Subprocess TB.2.2 + * Terminate Descendent Attempts Process UP.3 + * Overall Rollup Process RB.1.5 + * + * @see UtilityProcess#processEndAttempt(UtilityProcessRequest) UP.4 + * @see TerminationBehavior#processSequencingExitActionRules(TerminationRequest) TB.2.1 + * @see TerminationBehavior#processSequencingPostConditionRules(TerminationRequest) TB.2.2 + * @see UtilityProcess#processTerminateDescendentAttempts(UtilityProcessRequest) UP.3 + * @see RollupBehavior#overallRollup(RollupRequest) RB.1.5 + */ + public static TerminationBehaviorResult processTerminationRequest(TerminationRequest terminationRequest) { + ActivityTree activityTree = terminationRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + // If the sequencing session has not begun, there is nothing to terminate. + if (currentActivity == null) { + // 1.1 + return new TerminationBehaviorResult() + .setValidTerminationRequest(false).setException(SequencingException.TB231); + } + // 2 + // If the current activity has already been terminated, there is nothing to terminate. + if ((terminationRequest.getRequestType() == Type.Exit || terminationRequest.getRequestType() == Type.Abandon) + && !currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 2.1 + return new TerminationBehaviorResult() + .setValidTerminationRequest(false).setException(SequencingException.TB232); + } + // 3 + if (terminationRequest.getRequestType() == Type.Exit) { + boolean breakToNextCase = false; + // 3.1 + // Ensure the state of the current activity is up to date. + UtilityProcess.processEndAttempt(new UtilityProcessRequest(activityTree, currentActivity)); + // 3.2 + // Check if any of the current activity's ancestors need to terminate. + processSequencingExitActionRules(new TerminationRequest(terminationRequest.getRequestType(), activityTree, currentActivity)); + // 3.3 + boolean processedExit; + int count = 0; + SequencingRequest returnedSR = null; + do { + // 3.3.1 + processedExit = false; + // 3.3.2 + TerminationBehaviorResult terminationBehaviorResult = processSequencingPostConditionRules( + new TerminationRequest(terminationRequest.getRequestType(), activityTree, currentActivity)); + if (terminationBehaviorResult.getSequencingRequest() != null) { + count++; + returnedSR = terminationBehaviorResult.getSequencingRequest(); + } + // 3.3.3 + if (terminationBehaviorResult.getTerminationRequest() != null + && terminationBehaviorResult.getTerminationRequest().getRequestType() == Type.ExitAll) { + // 3.3.3.1 + terminationRequest.setRequestType(Type.ExitAll); + // 3.3.3.2 + // Process an Exit All Termination Request. + breakToNextCase = true; + break; + } + // 3.3.4 + // If we exit the parent of the current activity, + // move the current activity to the parent of the current activity + if (terminationBehaviorResult.getTerminationRequest() != null + && terminationBehaviorResult.getTerminationRequest().getRequestType() == Type.ExitParent) { + // 3.3.4.1 + // The root of the activity tree does not have a parent to exit. + if (!activityTree.isRoot(currentActivity)) { + // 3.3.4.1.1 + activityTree.getGlobalStateInformation().setCurrentActivity(currentActivity.getParentActivity()); + currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 3.3.4.1.2 + UtilityProcess.processEndAttempt(new UtilityProcessRequest(activityTree, currentActivity)); + // 3.3.4.1.3 + // Need to evaluate post conditions on the new current activity. + processedExit = true; + } else { // 3.3.4.2 + // 3.3.4.2.1 + return new TerminationBehaviorResult() + .setValidTerminationRequest(false).setException(SequencingException.TB234); + } + } else { // 3.3.5 + // 3.3.5.1 + // If the attempt on the root of the Activity Tree is ending without a Retry, + // the Sequencing Session also ends. + if (activityTree.isRoot(currentActivity) && (terminationBehaviorResult.getSequencingRequest() != null + && terminationBehaviorResult.getSequencingRequest().getRequestType() != SequencingRequest.Type.Retry)) { + // 3.3.5.1.1 + return new TerminationBehaviorResult().setValidTerminationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, + activityTree.getGlobalStateInformation().getCurrentActivity())); + } + } + } while (processedExit); // 3.4 + // 3.5 + if (!breakToNextCase) { + return new TerminationBehaviorResult().setValidTerminationRequest(true) + .setSequencingRequest(count == 1 ? returnedSR : null); + } + } + // 4 + if (terminationRequest.getRequestType() == Type.ExitAll) { + // 4.1 + // Has the completion subprocess and rollup been applied to the current activity yet? + if (currentActivity.getActivityStateInformation().isActivityIsActive()) { + // 4.1.1 + UtilityProcess.processEndAttempt(new UtilityProcessRequest(activityTree, currentActivity)); + } + // 4.2 + UtilityProcess.processTerminateDescendentAttempts( + new UtilityProcessRequest(activityTree, activityTree.getRoot())); + // 4.3 + UtilityProcess.processEndAttempt(new UtilityProcessRequest(activityTree, activityTree.getRoot())); + // 4.4 + // Move the current activity to the root of the activity tree. + activityTree.getGlobalStateInformation().setCurrentActivity(activityTree.getRoot()); + currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 4.5 + // Inform the sequencer that the sequencing session has ended. + return new TerminationBehaviorResult().setValidTerminationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, + activityTree.getGlobalStateInformation().getCurrentActivity())); + } else if (terminationRequest.getRequestType() == Type.SuspendAll) { // 5 + // 5.1 + // If the current activity is active or already suspended, + // suspend it and all of its descendents. + if (currentActivity.getActivityStateInformation().isActivityIsActive() + || currentActivity.getActivityStateInformation().isActivityIsSuspended()) { + // 5.1.1 + // Ensure that any status change to this activity is propagated through the entire activity tree. + RollupBehavior.overallRollup(new RollupRequest(activityTree, currentActivity)); + // 5.1.2 + activityTree.getGlobalStateInformation().setSuspendedActivity(currentActivity); + } else { // 5.2 + // 5.2.1 + // Make sure the current activity is not the root of the activity tree. + if (!activityTree.isRoot(currentActivity)) { + // 5.2.1.1 + activityTree.getGlobalStateInformation().setSuspendedActivity(currentActivity.getParentActivity()); + } else { // 5.2.2 + // 5.2.2.1 + // Nothing to suspend. + return new TerminationBehaviorResult().setValidTerminationRequest(false) + .setException(SequencingException.TB233); + } + } + // 5.3 + // From the suspended activity to the root of the activity tree, inclusive + List<Activity> activityPath = new LinkedList<>(); + Activity tmp = activityTree.getGlobalStateInformation().getSuspendedActivity(); + while (tmp != null) { + activityPath.add(tmp); + tmp = tmp.getParentActivity(); + } + // 5.4 + if (activityPath.isEmpty()) { + // 5.4.1 + // Nothing to suspend. + return new TerminationBehaviorResult().setValidTerminationRequest(false) + .setException(SequencingException.TB235); + } + // 5.5 + for (Activity activity : activityPath) { + // 5.5.1 + activity.getActivityStateInformation().setActivityIsActive(false); + // 5.5.2 + activity.getActivityStateInformation().setActivityIsSuspended(true); + } + // 5.6 + // Move the current activity to the root of the activity tree. + activityTree.getGlobalStateInformation().setCurrentActivity(activityTree.getRoot()); + // 5.7 + // Inform the sequencer that the sequencing session has ended. + return new TerminationBehaviorResult().setValidTerminationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, + activityTree.getGlobalStateInformation().getCurrentActivity())); + } else if (terminationRequest.getRequestType() == Type.Abandon) { // 6 + // 6.1 + currentActivity.getActivityStateInformation().setActivityIsActive(false); + // 6.2 + return new TerminationBehaviorResult().setValidTerminationRequest(true); + } else if (terminationRequest.getRequestType() == Type.AbandonAll) { // 7 + // 7.1 + // From the current activity to the root of the activity tree, inclusive + List<Activity> activityPath = new LinkedList<>(); + Activity tmp = currentActivity; + while (tmp != null) { + activityPath.add(tmp); + tmp = tmp.getParentActivity(); + } + // 7.2 + if (activityPath.isEmpty()) { + // 7.2.1 + // Nothing to abandon + return new TerminationBehaviorResult() + .setValidTerminationRequest(false) + .setException(SequencingException.TB236); + } + // 7.3 + for (Activity activity : activityPath) { + // 7.3.1 + activity.getActivityStateInformation().setActivityIsActive(false); + } + // 7.4 + // Move the current activity to the root of the activity tree. + activityTree.getGlobalStateInformation().setCurrentActivity(activityTree.getRoot()); + // 7.5 + // Inform the sequencer that the sequencing session has ended. + return new TerminationBehaviorResult() + .setValidTerminationRequest(true) + .setSequencingRequest(new SequencingRequest(SequencingRequest.Type.Exit, activityTree, + activityTree.getGlobalStateInformation().getCurrentActivity())); + } + // Undefined termination request. + return new TerminationBehaviorResult().setValidTerminationRequest(false) + .setException(SequencingException.TB237); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/UtilityProcess.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/UtilityProcess.java new file mode 100644 index 00000000..f32e6f40 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/UtilityProcess.java @@ -0,0 +1,399 @@ +package com.xboe.module.scorm.sn.api.behavior; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import com.xboe.module.scorm.sn.api.behavior.result.UtilityProcessResult; +import com.xboe.module.scorm.sn.api.request.RollupRequest; +import com.xboe.module.scorm.sn.api.request.UtilityProcessRequest; +import com.xboe.module.scorm.sn.api.util.BooleanUtils; +import com.xboe.module.scorm.sn.model.definition.DeliveryControls; +import com.xboe.module.scorm.sn.model.definition.LimitConditions; +import com.xboe.module.scorm.sn.model.definition.ObjectiveDescription; +import com.xboe.module.scorm.sn.model.definition.RuleCondition; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription.ConditionType; +import com.xboe.module.scorm.sn.model.tracking.ActivityProgressInformation; +import com.xboe.module.scorm.sn.model.tracking.AttemptProgressInformation; +import com.xboe.module.scorm.sn.model.tracking.ObjectiveProgressInformation; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class UtilityProcess { + + /** + * Limit Conditions Check Process [UP.1] + * + * For an activity; returns True if any of the activity's limit conditions have been violated. + * + * Reference: + * Activity Attempt Count TM.1.2.1 + * Activity Progress Status TM.1.2.1 + * Activity Absolute Duration TM.1.2.1 + * Activity Experienced Duration TM.1.2.1 + * Attempt Progress Status TM.1.2.2 + * Attempt Absolute Duration TM.1.2.2 + * Attempt Experienced Duration TM.1.2.2 + * Limit Condition Activity Absolute Duration Control SM.3 + * Limit Condition Activity Absolute Duration Limit SM.3 + * Limit Condition Activity Experienced Duration Control SM.3 + * Limit Condition Activity Experienced Duration Limit SM.3 + * Limit Condition Attempt Absolute Duration Control SM.3 + * Limit Condition Attempt Absolute Duration Limit SM.3 + * Limit Condition Attempt Experienced Duration Control SM.3 + * Limit Condition Attempt Control SM.3 + * Limit Condition Attempt Limit SM.3 + * Limit Condition Begin Time Limit SM.3 + * Limit Condition Begin Time Limit Control SM.3 + * Limit Condition End Time Limit SM.3 + * Limit Condition End Time Limit Control SM.3 + * Tracked SM.11 + */ + public static UtilityProcessResult processLimitConditionsCheck(UtilityProcessRequest utilityProcessRequest) { + Activity targetActivity = utilityProcessRequest.getTargetActivity(); + LimitConditions limitConditions = targetActivity.getSequencingDefinition().getLimitConditions(); + // 1 + // If the activity is not tracked, its limit conditions cannot be violated. + if (!targetActivity.getSequencingDefinition().getDeliveryControls().isTracked()) { + // 1.1 + // Activity is not tracked, no limit conditions can be violated, exit UP.1. + return new UtilityProcessResult().setLimitConditionViolated(false); + } + // 2 + // Only need to check activities that will begin a new attempt. + if (targetActivity.getActivityStateInformation().isActivityIsActive() + || targetActivity.getActivityStateInformation().isActivityIsSuspended()) { + // 2.1 + return new UtilityProcessResult().setLimitConditionViolated(false); + } + // 3 + if (limitConditions.isAttemptControl()) { + // 3.1 + if (targetActivity.getActivityProgressInformation().isActivityProgressStatus() + && targetActivity.getActivityProgressInformation().getActivityAttemptCount().getValue() + >= limitConditions.getAttemptLimit().getValue()) { + // 3.1.1 + // Limit conditions have been violated. + return new UtilityProcessResult().setLimitConditionViolated(true); + } + } + // The following code (from 4 to 9) is optionally in this SCORM 2004 4th edition, refer SN-C-64 + // 10 + // No limit conditions have been violated. + return new UtilityProcessResult().setLimitConditionViolated(false); + } + + /** + * Sequencing Rules Check Process [UP.2] + * + * For an activity and a set of Rule Actions; returns the action to apply or Nil. + * + * Reference: + * Rule Action SM.2 + * Sequencing Rule Check Subprocess UP.2.1 + * Sequencing Rule Description SM.2 + * + * @see UtilityProcess#processSequencingRuleCheck(UtilityProcessRequest) UP.2.1 + */ + public static UtilityProcessResult processSequencingRulesCheck(UtilityProcessRequest utilityProcessRequest) { + Activity activity = utilityProcessRequest.getTargetActivity(); + List<String> ruleActions = Arrays.asList(utilityProcessRequest.getRuleActions()); + // 1 + // Make sure the activity rules to evaluate + if (!activity.getSequencingDefinition().getSequencingRuleDescriptions().isEmpty()) { + // 1.1 + List<SequencingRuleDescription> ruleList = new LinkedList<>(); + for (SequencingRuleDescription sequencingRuleDescription : activity.getSequencingDefinition().getSequencingRuleDescriptions()) { + if (sequencingRuleDescription.getConditionType() == utilityProcessRequest.getConditionType() && + ruleActions.contains(sequencingRuleDescription.getRuleAction().getValue()) + && !sequencingRuleDescription.getRuleAction().getValue().equals("Ignore")) { + ruleList.add(sequencingRuleDescription); + } + } + // 1.2 + for (SequencingRuleDescription rule : ruleList) { + // 1.2.1 + // Evaluate each rule, one at a time + UtilityProcessResult utilityProcessResult = processSequencingRuleCheck( + new UtilityProcessRequest(utilityProcessRequest.getTargetActivityTree(), activity) + .setSequencingRuleDescription(rule)); + // 1.2.2 + if (utilityProcessResult.getResult() != null && utilityProcessResult.getResult()) { + // 1.2.2.1 + // Stop at the first rule that evaluates to true - perform the associated action. + return new UtilityProcessResult().setAction(rule.getRuleAction().getValue()); + } + } + } + return new UtilityProcessResult().setAction(null); + } + + /** + * Sequencing Rule Check Subprocess [UP.2.1] + * + * For an activity and a Sequencing Rule; returns True if the rule applies, False if the rule does not apply, + * and Unknown if the condition(s) cannot be evaluated. + * + * Reference: + * Rule Combination SM.2 + * Rule Condition SM.2 + * Rule Condition Operator SM.2 + * Sequencing Rule Description SM.2 + * Track Model TM + */ + public static UtilityProcessResult processSequencingRuleCheck(UtilityProcessRequest utilityProcessRequest) { + // 1 + // This is used to keep track of the evaluation of the rule's conditions. + List<Boolean> evaluateResults = new LinkedList<>(); + // 2 + for (RuleCondition ruleCondition : utilityProcessRequest.getSequencingRuleDescription().getRuleConditions()) { + // 2.1 + // evaluate each condition against the activity's tracking information. + Boolean evaluateResult = evaluateRuleCondition(ruleCondition, utilityProcessRequest.getTargetActivity()); + // 2.2 + if (ruleCondition.getOperator().getValue().equals("Not")) { + // 2.2.1 + // Negating "unknown" results in "unknown". + evaluateResult = BooleanUtils.negate(evaluateResult); + } + // 2.3 + // Add the evaluation of this condition to the set of evaluated conditions. + evaluateResults.add(evaluateResult); + } + // 3 + // If there are no defined conditions for the rule, the rule does not apply. + if (evaluateResults.isEmpty()) { + return new UtilityProcessResult().setResult(null); + } + // 4 + // 'And' or 'Or' the set of evaluated conditions, based on the sequencing rule definition. + Boolean result; + if (utilityProcessRequest.getSequencingRuleDescription().getConditionCombination().getValue().equals("All")) { + result = BooleanUtils.and(evaluateResults.toArray(new Boolean[0])); + } else { + result = BooleanUtils.or(evaluateResults.toArray(new Boolean[0])); + } + return new UtilityProcessResult().setResult(result); + } + + /** + * Terminate Descendent Attempts Process [UP.3] + * + * For an activity. + * + * Reference: + * Current Activity AM.1.2 + * End Attempt Process UP.4 + * + * @see UtilityProcess#processEndAttempt(UtilityProcessRequest) UP.4 + */ + public static void processTerminateDescendentAttempts(UtilityProcessRequest utilityProcessRequest) { + Activity identifiedActivity = utilityProcessRequest.getTargetActivity(); + ActivityTree activityTree = utilityProcessRequest.getTargetActivityTree(); + Activity currentActivity = activityTree.getGlobalStateInformation().getCurrentActivity(); + // 1 + Activity commonAncestor = activityTree.findCommonAncestorFor(identifiedActivity, currentActivity); + // 2 + // The current activity must have already been exited. + // From the current activity to the common ancestor, exclusive of the current activity and the common ancestor + List<Activity> activityPath = new LinkedList<>(); + if (commonAncestor != null) { + Activity tmp = currentActivity.getParentActivity(); + while (tmp != null && !tmp.equals(commonAncestor)) { + activityPath.add(tmp); + tmp = tmp.getParentActivity(); + } + } + // 3 + // There are some activities that need to be terminated. + if (!activityPath.isEmpty()) { + // 3.1 + for (Activity activity : activityPath) { + // 3.1.1 + // End the current attempt on each activity. + processEndAttempt(new UtilityProcessRequest(activityTree, activity)); + } + } + } + + /** + * End Attempt Process [UP.4] + * + * For an activity. + * + * Reference: + * Activity is Active AM.1.1 + * Activity is Suspended AM.1.1 + * Attempt Completion Status TM.1.2.2 + * Attempt Progress Status TM.1.2.2 + * Completion Set By Content SM.11 + * Objective Contributes to Rollup SM.6 + * Objective Progress Status TM.1.1 + * Objective Satisfied Status TM.1.1 + * Objective Set By Content SM.11 + * Tracked SM.11 + * Overall Rollup Process RB.1.5 + * + * @see RollupBehavior#overallRollup(RollupRequest) RB.1.5 + */ + public static void processEndAttempt(UtilityProcessRequest utilityProcessRequest) { + Activity targetActivity = utilityProcessRequest.getTargetActivity(); + DeliveryControls deliveryControls = targetActivity.getSequencingDefinition().getDeliveryControls(); + // 1 + if (targetActivity.isLeaf()) { + // TODO 1.1 + if (deliveryControls.isTracked()) { +// SCORM.getInstance().mapRuntimeDataToTrackingInfo(targetActivity); + // 1.1.1 + // The sequencer will not affect the state of suspended activities. + if (!targetActivity.getActivityStateInformation().isActivityIsSuspended()) { + // 1.1.1.1 + // Should the sequencer set the completion status of the activity? + if (!deliveryControls.isCompletionSetByContent()) { + // 1.1.1.1.1 + // Did the content inform the sequencer of the activity's completion status? + if (!targetActivity.getAttemptProgressInformation().isAttemptProgressStatus()) { + // 1.1.1.1.1.1 + targetActivity.getAttemptProgressInformation().setAttemptProgressStatus(true); + // 1.1.1.1.1.2 + targetActivity.getAttemptProgressInformation().setAttemptCompletionStatus(true); + } + } + // 1.1.1.2 + // Should the sequencer set the objective status of the activity? + if (!deliveryControls.isObjectiveSetByContent()) { + // 1.1.1.2.1 + for (ObjectiveDescription objectiveDescription : targetActivity.getSequencingDefinition().getObjectiveDescriptions()) { + // 1.1.1.2.1.1 + if (objectiveDescription.isObjectiveContributesToRollup()) { + // 1.1.1.2.1.1.1 + // Did the content inform the sequencer of the activity's rolled-up objective status? + if (!objectiveDescription.getObjectiveProgressInformation().isObjectiveProgressStatus()) { + // 1.1.1.2.1.1.1.1 + objectiveDescription.getObjectiveProgressInformation().setObjectiveProgressStatus(true); + // 1.1.1.2.1.1.1.2 + objectiveDescription.getObjectiveProgressInformation().setObjectiveSatisfiedStatus(true); + } + } + } + } + } + } + // TODO SN P115 + } else { // 2 The activity has children. + // 2.1 + // The suspended status of the parent is dependent on the suspended status of its children. + boolean flag = false; + for (Activity child : targetActivity.getChildren()) { + if (child.getActivityStateInformation().isActivityIsSuspended()) { + flag = true; + break; + } + } + if (flag) { + // 2.1.1 + targetActivity.getActivityStateInformation().setActivityIsSuspended(true); + } else { // 2.2 + // 2.2.1 + targetActivity.getActivityStateInformation().setActivityIsSuspended(false); + } + } + // 3 + // The current attempt on this activity has ended. + targetActivity.getActivityStateInformation().setActivityIsActive(false); + // 4 + // Ensure that any status change to this activity is propagated through the entire activity tree. + RollupBehavior.overallRollup( + new RollupRequest(utilityProcessRequest.getTargetActivityTree(), targetActivity)); + } + + /** + * SN-3-17 + */ + private static Boolean evaluateRuleCondition(RuleCondition ruleCondition, Activity targetActivity) { + ObjectiveDescription objectiveDescription = targetActivity + .findAssociatedObjectiveByID(ruleCondition.getReferencedObjective()); + if (objectiveDescription == null) { + return null; + } + ObjectiveProgressInformation objectiveProgressInformation = objectiveDescription + .getObjectiveProgressInformation(); + AttemptProgressInformation attemptProgressInformation = targetActivity.getAttemptProgressInformation(); + ActivityProgressInformation activityProgressInformation = targetActivity.getActivityProgressInformation(); + LimitConditions limitConditions = targetActivity.getSequencingDefinition().getLimitConditions(); + switch (ruleCondition.getRuleCondition().getValue()) { + case "Always": + return true; + case "Satisfied": + return objectiveProgressInformation.isObjectiveProgressStatus() ? + objectiveProgressInformation.isObjectiveSatisfiedStatus() : null; + case "Objective Status Known": + return objectiveProgressInformation.isObjectiveProgressStatus(); + case "Objective Measure Known": + return objectiveProgressInformation.isObjectiveMeasureStatus(); + case "Objective Measure Greater Than": + return objectiveProgressInformation.isObjectiveMeasureStatus() ? + objectiveProgressInformation.getObjectiveNormalizedMeasure() + .compareTo(ruleCondition.getMeasureThreshold()) > 0 : null; + case "Objective Measure Less Than": + return objectiveProgressInformation.isObjectiveMeasureStatus() ? + objectiveProgressInformation.getObjectiveNormalizedMeasure() + .compareTo(ruleCondition.getMeasureThreshold()) < 0 : null; + case "Completed": + return objectiveProgressInformation.isObjectiveCompletionProgressStatus() ? + objectiveProgressInformation.isObjectiveCompletionStatus() : null; +// return attemptProgressInformation.isAttemptProgressStatus() ? +// attemptProgressInformation.isAttemptCompletionStatus() : null; + case "Activity Progress Known": + return objectiveProgressInformation.isObjectiveCompletionProgressStatus(); +// return attemptProgressInformation.isAttemptProgressStatus(); + case "Attempted": + return activityProgressInformation.getActivityAttemptCount().getValue() > 0; + case "Attempt Limit Exceeded": + return activityProgressInformation.isActivityProgressStatus() && limitConditions.isAttemptControl() + && activityProgressInformation.getActivityAttemptCount().getValue() >= limitConditions.getAttemptLimit().getValue(); + } + return null; + } + + /** + * Check Activity Process [UP.5] + * + * For an activity; returns True if the activity is disabled or violates any of its limit conditions. + * + * Reference: + * Disabled Rules SM.2 + * Limit Conditions Check Process UP.1 + * Sequencing Rules Check Process UP.2 + * + * @see UtilityProcess#processLimitConditionsCheck(UtilityProcessRequest) UP.1 + * @see UtilityProcess#processSequencingRulesCheck(UtilityProcessRequest) UP.2 + */ + public static UtilityProcessResult processCheckActivity(UtilityProcessRequest utilityProcessRequest) { + Activity targetActivity = utilityProcessRequest.getTargetActivity(); + // 1 + // Make sure the activity is not disabled. + UtilityProcessResult utilityProcessResult = processSequencingRulesCheck( + new UtilityProcessRequest(utilityProcessRequest.getTargetActivityTree(), targetActivity) + .setConditionType(ConditionType.PRECONDITION).setRuleActions("Disabled")); + // 2 + if (utilityProcessResult.getAction() != null) { + // 2.1 + return new UtilityProcessResult().setResult(true); + } + // 3 + // Make sure the activity does not violate any limit condition. + utilityProcessResult = processLimitConditionsCheck( + new UtilityProcessRequest(utilityProcessRequest.getTargetActivityTree(), targetActivity)); + // 4 + if (utilityProcessResult.isLimitConditionViolated()) { + // 4.1 + return new UtilityProcessResult().setResult(true); + } + // 5 + // Activity is allowed. + return new UtilityProcessResult().setResult(false); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/common/TraversalDirection.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/common/TraversalDirection.java new file mode 100644 index 00000000..0be359a7 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/common/TraversalDirection.java @@ -0,0 +1,6 @@ +package com.xboe.module.scorm.sn.api.behavior.common; + +public enum TraversalDirection { + Forward, + Backward +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/BaseResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/BaseResult.java new file mode 100644 index 00000000..0516bb46 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/BaseResult.java @@ -0,0 +1,15 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +public class BaseResult { + + private SequencingException exception; + + public SequencingException getException() { + return exception; + } + + public BaseResult setException(SequencingException exception) { + this.exception = exception; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/DeliveryBehaviorResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/DeliveryBehaviorResult.java new file mode 100644 index 00000000..701472e0 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/DeliveryBehaviorResult.java @@ -0,0 +1,20 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +public class DeliveryBehaviorResult extends BaseResult { + + private boolean validDeliveryRequest; + + public boolean isValidDeliveryRequest() { + return validDeliveryRequest; + } + + public DeliveryBehaviorResult setValidDeliveryRequest(boolean validDeliveryRequest) { + this.validDeliveryRequest = validDeliveryRequest; + return this; + } + + @Override + public DeliveryBehaviorResult setException(SequencingException exception) { + return (DeliveryBehaviorResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/NavigationBehaviorResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/NavigationBehaviorResult.java new file mode 100644 index 00000000..67a1399e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/NavigationBehaviorResult.java @@ -0,0 +1,43 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +import com.xboe.module.scorm.sn.api.request.SequencingRequest; +import com.xboe.module.scorm.sn.api.request.TerminationRequest; + +public class NavigationBehaviorResult extends BaseResult { + + private boolean validNavigationRequest; + private TerminationRequest terminationRequest; + private SequencingRequest sequencingRequest; + + public boolean isValidNavigationRequest() { + return validNavigationRequest; + } + + public NavigationBehaviorResult setValidNavigationRequest(boolean validNavigationRequest) { + this.validNavigationRequest = validNavigationRequest; + return this; + } + + public TerminationRequest getTerminationRequest() { + return terminationRequest; + } + + public NavigationBehaviorResult setTerminationRequest(TerminationRequest terminationRequest) { + this.terminationRequest = terminationRequest; + return this; + } + + public SequencingRequest getSequencingRequest() { + return sequencingRequest; + } + + public NavigationBehaviorResult setSequencingRequest(SequencingRequest sequencingRequest) { + this.sequencingRequest = sequencingRequest; + return this; + } + + @Override + public NavigationBehaviorResult setException(SequencingException exception) { + return (NavigationBehaviorResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/OverallSequencingResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/OverallSequencingResult.java new file mode 100644 index 00000000..427f243f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/OverallSequencingResult.java @@ -0,0 +1,40 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +public class OverallSequencingResult extends BaseResult { + + private final boolean success; + private final boolean exit; + private Boolean endSequencingSession; + + public OverallSequencingResult(boolean success) { + this.success = success; + this.exit = false; + } + + public OverallSequencingResult(boolean success, boolean exit) { + this.success = success; + this.exit = exit; + } + + public boolean isSuccess() { + return success; + } + + public boolean isExit() { + return exit; + } + + public Boolean getEndSequencingSession() { + return endSequencingSession; + } + + public OverallSequencingResult setEndSequencingSession(Boolean endSequencingSession) { + this.endSequencingSession = endSequencingSession; + return this; + } + + @Override + public OverallSequencingResult setException(SequencingException exception) { + return (OverallSequencingResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/RollupBehaviorResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/RollupBehaviorResult.java new file mode 100644 index 00000000..1d062bac --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/RollupBehaviorResult.java @@ -0,0 +1,31 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +public class RollupBehaviorResult extends BaseResult { + + private boolean childIsIncludedInRollup; + + private Boolean evaluation; + + public boolean isChildIsIncludedInRollup() { + return childIsIncludedInRollup; + } + + public RollupBehaviorResult setChildIsIncludedInRollup(boolean childIsIncludedInRollup) { + this.childIsIncludedInRollup = childIsIncludedInRollup; + return this; + } + + public Boolean getEvaluation() { + return evaluation; + } + + public RollupBehaviorResult setEvaluation(Boolean evaluation) { + this.evaluation = evaluation; + return this; + } + + @Override + public RollupBehaviorResult setException(SequencingException exception) { + return (RollupBehaviorResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SelectionAndRandomizationBehaviorResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SelectionAndRandomizationBehaviorResult.java new file mode 100644 index 00000000..31e7e53b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SelectionAndRandomizationBehaviorResult.java @@ -0,0 +1,9 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +public class SelectionAndRandomizationBehaviorResult extends BaseResult { + + @Override + public SelectionAndRandomizationBehaviorResult setException(SequencingException exception) { + return (SelectionAndRandomizationBehaviorResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SequencingBehaviorResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SequencingBehaviorResult.java new file mode 100644 index 00000000..77d098d5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SequencingBehaviorResult.java @@ -0,0 +1,94 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +import com.xboe.module.scorm.sn.api.behavior.common.TraversalDirection; +import com.xboe.module.scorm.sn.api.request.DeliveryRequest; +import com.xboe.module.scorm.sn.model.tree.Activity; + +public class SequencingBehaviorResult extends BaseResult { + + private boolean validSequencingRequest; + private DeliveryRequest deliveryRequest; + private Boolean endSequencingSession; + private Activity nextActivity; + private TraversalDirection traversalDirection; + private boolean deliverable; + private Activity identifiedActivity; + private boolean reachable; + + public boolean isValidSequencingRequest() { + return validSequencingRequest; + } + + public SequencingBehaviorResult setValidSequencingRequest(boolean validSequencingRequest) { + this.validSequencingRequest = validSequencingRequest; + return this; + } + + public DeliveryRequest getDeliveryRequest() { + return deliveryRequest; + } + + public SequencingBehaviorResult setDeliveryRequest(DeliveryRequest deliveryRequest) { + this.deliveryRequest = deliveryRequest; + return this; + } + + public Boolean getEndSequencingSession() { + return endSequencingSession; + } + + public SequencingBehaviorResult setEndSequencingSession(Boolean endSequencingSession) { + this.endSequencingSession = endSequencingSession; + return this; + } + + public Activity getNextActivity() { + return nextActivity; + } + + public SequencingBehaviorResult setNextActivity(Activity nextActivity) { + this.nextActivity = nextActivity; + return this; + } + + public TraversalDirection getTraversalDirection() { + return traversalDirection; + } + + public SequencingBehaviorResult setTraversalDirection(TraversalDirection traversalDirection) { + this.traversalDirection = traversalDirection; + return this; + } + + public boolean isDeliverable() { + return deliverable; + } + + public SequencingBehaviorResult setDeliverable(boolean deliverable) { + this.deliverable = deliverable; + return this; + } + + public Activity getIdentifiedActivity() { + return identifiedActivity; + } + + public SequencingBehaviorResult setIdentifiedActivity(Activity identifiedActivity) { + this.identifiedActivity = identifiedActivity; + return this; + } + + public boolean isReachable() { + return reachable; + } + + public SequencingBehaviorResult setReachable(boolean reachable) { + this.reachable = reachable; + return this; + } + + @Override + public SequencingBehaviorResult setException(SequencingException exception) { + return (SequencingBehaviorResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SequencingException.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SequencingException.java new file mode 100644 index 00000000..eb59e45c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/SequencingException.java @@ -0,0 +1,77 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +public enum SequencingException { + NB211("NB.2.1-1", "Current Activity is already defined / Sequencing session has already begun"), + NB212("NB.2.1-2", "Current Activity is not defined / Sequencing session has not begun"), + NB213("NB.2.1-3", "Suspended Activity is not defined"), + NB214("NB.2.1-4", "Flow Sequencing Control Mode violation"), + NB215("NB.2.1-5", "Flow or Forward Only Sequencing Control Mode violation"), + NB216("NB.2.1-6", "No activity is \"previous\" to the root"), + NB217("NB.2.1-7", "Unsupported navigation request"), + NB218("NB.2.1-8", "Choice Exit Sequencing Control Mode violation"), + NB219("NB.2.1-9", "No activities to consider"), + NB2110("NB.2.1-10", "Choice Sequencing Control Mode violation"), + NB2111("NB.2.1-11", "Target activity does not exist"), + NB2112("NB.2.1-12", "Current Activity already terminated"), + NB2113("NB.2.1-13", "Undefined navigation request"), + TB231("TB.2.3-1", "Current Activity is not defined / Sequencing session has not begun"), + TB232("TB.2.3-2", "Current Activity already terminated"), + TB233("TB.2.3-3", "Cannot suspend an inactive root"), + TB234("TB.2.3-4", "Activity tree root has no parent"), + TB235("TB.2.3-5", "Nothing to suspend; No active activities"), + TB236("TB.2.3-6", "Nothing to abandon; No active activities"), + TB237("TB.2.3-7", "Undefined termination request"), + SB211("SB.2.1-1", "Last activity in the tree"), + SB212("SB.2.1-2", "Cluster has no available children"), + SB213("SB.2.1-3", "No activity is \"previous\" to the root"), + SB214("SB.2.1-4", "Forward Only Sequencing Control Mode violation"), + SB221("SB.2.2-1", "Flow Sequencing Control Mode violation"), + SB222("SB.2.2-2", "Activity unavailable"), + SB241("SB.2.4-1", "Forward Traversal Blocked"), + SB242("SB.2.4-2", "Forward Only Sequencing Control Mode violation"), + SB243("SB.2.4-3", "No activity is \"previous\" to the root"), + SB251("SB.2.5-1", "Current Activity is defined / Sequencing session already begun"), + SB261("SB.2.6-1", "Current Activity is defined / Sequencing session already begun"), + SB262("SB.2.6-2", "No Suspended Activity defined"), + SB271("SB.2.7-1", "Current Activity is not defined / Sequencing session has not begun"), + SB272("SB.2.7-2", "Flow Sequencing Control Mode violation"), + SB281("SB.2.8-1", "Current Activity is not defined / Sequencing session has not begun"), + SB282("SB.2.8-2", "Flow Sequencing Control Mode violation"), + SB291("SB.2.9-1", "No target for Choice"), + SB292("SB.2.9-2", "Target activity does not exist or is unavailable"), + SB293("SB.2.9-3", "Target activity hidden from choice"), + SB294("SB.2.9-4", "Choice Sequencing Control Mode violation"), + SB295("SB.2.9-5", "No activities to consider"), + SB296("SB.2.9-6", "Unable to activate target; target is not a child of the Current Activity"), + SB297("SB.2.9-7", "Choice Exit Sequencing Control Mode violation"), + SB298("SB.2.9-8", "Unable to choose target activity – constrained choice"), + SB299("SB.2.9-9", "Choice request prevented by Flow-only activity"), + SB2101("SB.2.10-1", "Current Activity is not defined / Sequencing session has not begun"), + SB2102("SB.2.10-2", "Current Activity is active or suspended"), + SB2103("SB.2.10-3", "Flow Sequencing Control Mode violation"), + SB2111("SB.2.11-1", "Current Activity is not defined / Sequencing session has not begun"), + SB2112("SB.2.11-2", "Current Activity has not been terminated"), + SB2121("SB.2.12-1", "Undefined sequencing request"), + SB2131("SB.2.13-1", "Current Activity is not defined / Sequencing session has not begun"), + DB111("DB.1.1-1", "Cannot deliver a non-leaf activity"), + DB112("DB.1.1-2", "Nothing to deliver"), + DB113("DB.1.1-3", "Activity unavailable"), + DB21("DB.2-1", "Identified activity is already active"); + + private final String code; + private final String description; + + SequencingException(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/TerminationBehaviorResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/TerminationBehaviorResult.java new file mode 100644 index 00000000..b605765c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/TerminationBehaviorResult.java @@ -0,0 +1,45 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +import com.xboe.module.scorm.sn.api.request.SequencingRequest; +import com.xboe.module.scorm.sn.api.request.TerminationRequest; + +public class TerminationBehaviorResult extends BaseResult { + + private boolean validTerminationRequest; + + private SequencingRequest sequencingRequest; + + private TerminationRequest terminationRequest; + + public boolean isValidTerminationRequest() { + return validTerminationRequest; + } + + public TerminationBehaviorResult setValidTerminationRequest(boolean validTerminationRequest) { + this.validTerminationRequest = validTerminationRequest; + return this; + } + + public SequencingRequest getSequencingRequest() { + return sequencingRequest; + } + + public TerminationBehaviorResult setSequencingRequest(SequencingRequest sequencingRequest) { + this.sequencingRequest = sequencingRequest; + return this; + } + + public TerminationRequest getTerminationRequest() { + return terminationRequest; + } + + public TerminationBehaviorResult setTerminationRequest(TerminationRequest terminationRequest) { + this.terminationRequest = terminationRequest; + return this; + } + + @Override + public TerminationBehaviorResult setException(SequencingException exception) { + return (TerminationBehaviorResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/UtilityProcessResult.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/UtilityProcessResult.java new file mode 100644 index 00000000..6fa4249c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/behavior/result/UtilityProcessResult.java @@ -0,0 +1,42 @@ +package com.xboe.module.scorm.sn.api.behavior.result; + +public class UtilityProcessResult extends BaseResult { + + private String action; + + private Boolean result; + + private boolean limitConditionViolated; + + public String getAction() { + return action; + } + + public UtilityProcessResult setAction(String action) { + this.action = action; + return this; + } + + public Boolean getResult() { + return result; + } + + public UtilityProcessResult setResult(Boolean result) { + this.result = result; + return this; + } + + public boolean isLimitConditionViolated() { + return limitConditionViolated; + } + + public UtilityProcessResult setLimitConditionViolated(boolean limitConditionViolated) { + this.limitConditionViolated = limitConditionViolated; + return this; + } + + @Override + public UtilityProcessResult setException(SequencingException exception) { + return (UtilityProcessResult) super.setException(exception); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/EventTranslator.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/EventTranslator.java new file mode 100644 index 00000000..c1c180bd --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/EventTranslator.java @@ -0,0 +1,48 @@ +package com.xboe.module.scorm.sn.api.event; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.sn.api.request.NavigationRequest; +import com.xboe.module.scorm.sn.api.request.NavigationRequest.Type; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class EventTranslator { + + public static NavigationRequest translateEventToRequestType(@NotNull NavigationEvent event, + ActivityTree activityTree, Activity activity) { + return new NavigationRequest(getRequestType(event), activityTree, activity); + } + + private static NavigationRequest.Type getRequestType(NavigationEvent event) { + if (event == null) { + throw new IllegalArgumentException(); + } + switch (event.getType()) { + case Start: + return Type.Start; + case ResumeAll: + return Type.ResumeAll; + case Continue: + return Type.Continue; + case Previous: + return Type.Previous; + case Choose: + return Type.Choice; + case Jump: + return Type.Jump; + case Abandon: + return Type.Abandon; + case AbandonAll: + return Type.AbandonAll; + case SuspendAll: + return Type.SuspendAll; + case UnqualifiedExit: + return Type.Exit; + case ExitAll: + return Type.ExitAll; + } + throw new IllegalArgumentException(); + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/EventType.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/EventType.java new file mode 100644 index 00000000..2f7814ed --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/EventType.java @@ -0,0 +1,33 @@ +package com.xboe.module.scorm.sn.api.event; + +public enum EventType { + Start(true, false), + ResumeAll(true, false), + Continue(true, true), + Previous(true, true), + Choose(true, true), + Jump(false, true), + Abandon(true, true), + AbandonAll(true, true), + SuspendAll(true, true), + UnqualifiedExit(true, false), + ExitAll(true, true); + + private final boolean triggeredByLMS; + private final boolean triggeredBySCO; + + EventType(boolean triggeredByLMS, boolean triggeredBySCO) { + this.triggeredByLMS = triggeredByLMS; + this.triggeredBySCO = triggeredBySCO; + } + + public boolean isTriggeredByLMS() { + return triggeredByLMS; + } + + public boolean isTriggeredBySCO() { + return triggeredBySCO; + } +} + + diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/NavigationEvent.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/NavigationEvent.java new file mode 100644 index 00000000..da25de7c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/event/NavigationEvent.java @@ -0,0 +1,25 @@ +package com.xboe.module.scorm.sn.api.event; + +public class NavigationEvent { + + private final EventType type; + + private final String targetActivityID; + + public NavigationEvent(EventType type) { + this(type, null); + } + + public NavigationEvent(EventType type, String targetActivityID) { + this.type = type; + this.targetActivityID = targetActivityID; + } + + public EventType getType() { + return type; + } + + public String getTargetActivityID() { + return targetActivityID; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/DeliveryRequest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/DeliveryRequest.java new file mode 100644 index 00000000..a8fa200b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/DeliveryRequest.java @@ -0,0 +1,21 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class DeliveryRequest extends Request { + + public DeliveryRequest(ActivityTree targetActivityTree, Activity targetActivity) { + super(targetActivityTree, targetActivity); + } + + @Override + public DeliveryRequest setTargetActivityTree(ActivityTree targetActivityTree) { + return (DeliveryRequest) super.setTargetActivityTree(targetActivityTree); + } + + @Override + public DeliveryRequest setTargetActivity(Activity targetActivity) { + return (DeliveryRequest) super.setTargetActivity(targetActivity); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/NavigationRequest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/NavigationRequest.java new file mode 100644 index 00000000..3f50eae8 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/NavigationRequest.java @@ -0,0 +1,52 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class NavigationRequest extends Request { + + private Type requestType; + + public NavigationRequest(@NotNull Type requestType, ActivityTree targetActivityTree, Activity targetActivity) { + super(targetActivityTree, targetActivity); + this.requestType = requestType; + } + + public Type getRequestType() { + return requestType; + } + + public NavigationRequest setRequestType(Type requestType) { + this.requestType = requestType; + return this; + } + + @Override + public NavigationRequest setTargetActivityTree(ActivityTree targetActivityTree) { + return (NavigationRequest) super.setTargetActivityTree(targetActivityTree); + } + + @Override + public NavigationRequest setTargetActivity(Activity targetActivity) { + return (NavigationRequest) super.setTargetActivity(targetActivity); + } + + public enum Type { + Start, + ResumeAll, + Continue, + Previous, + Forward, + Backward, + Choice, + Jump, + Exit, + ExitAll, + SuspendAll, + Abandon, + AbandonAll; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/Request.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/Request.java new file mode 100644 index 00000000..528b6eab --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/Request.java @@ -0,0 +1,34 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public abstract class Request { + + private ActivityTree targetActivityTree; + + private Activity targetActivity; + + public Request(ActivityTree targetActivityTree, Activity targetActivity) { + this.targetActivityTree = targetActivityTree; + this.targetActivity = targetActivity; + } + + public ActivityTree getTargetActivityTree() { + return targetActivityTree; + } + + public Request setTargetActivityTree(ActivityTree targetActivityTree) { + this.targetActivityTree = targetActivityTree; + return this; + } + + public Activity getTargetActivity() { + return targetActivity; + } + + public Request setTargetActivity(Activity targetActivity) { + this.targetActivity = targetActivity; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/RollupRequest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/RollupRequest.java new file mode 100644 index 00000000..443ea3b2 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/RollupRequest.java @@ -0,0 +1,66 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.xboe.module.scorm.sn.model.definition.RollupRuleDescription; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class RollupRequest extends Request { + + private String rollupAction; + + private RollupRuleDescription rollupRule; + + private boolean useDefaultRollupRule; + + private RollupRuleDescription defaultRollupRule; + + public RollupRequest(ActivityTree targetActivityTree, Activity targetActivity) { + super(targetActivityTree, targetActivity); + } + + @Override + public RollupRequest setTargetActivityTree(ActivityTree targetActivityTree) { + return (RollupRequest) super.setTargetActivityTree(targetActivityTree); + } + + @Override + public RollupRequest setTargetActivity(Activity targetActivity) { + return (RollupRequest) super.setTargetActivity(targetActivity); + } + + public String getRollupAction() { + return rollupAction; + } + + public RollupRequest setRollupAction(String rollupAction) { + this.rollupAction = rollupAction; + return this; + } + + public RollupRuleDescription getRollupRule() { + return rollupRule; + } + + public RollupRequest setRollupRule(RollupRuleDescription rollupRule) { + this.rollupRule = rollupRule; + return this; + } + + public boolean isUseDefaultRollupRule() { + return useDefaultRollupRule; + } + + public RollupRequest setUseDefaultRollupRule(boolean useDefaultRollupRule) { + this.useDefaultRollupRule = useDefaultRollupRule; + return this; + } + + public RollupRuleDescription getDefaultRollupRule() { + return defaultRollupRule; + } + + public RollupRequest setDefaultRollupRule(RollupRuleDescription defaultRollupRule) { + this.defaultRollupRule = defaultRollupRule; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/SelectionAndRandomizationRequest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/SelectionAndRandomizationRequest.java new file mode 100644 index 00000000..5013b127 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/SelectionAndRandomizationRequest.java @@ -0,0 +1,21 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class SelectionAndRandomizationRequest extends Request { + + public SelectionAndRandomizationRequest(ActivityTree targetActivityTree, Activity targetActivity) { + super(targetActivityTree, targetActivity); + } + + @Override + public SelectionAndRandomizationRequest setTargetActivityTree(ActivityTree targetActivityTree) { + return (SelectionAndRandomizationRequest) super.setTargetActivityTree(targetActivityTree); + } + + @Override + public SelectionAndRandomizationRequest setTargetActivity(Activity targetActivity) { + return (SelectionAndRandomizationRequest) super.setTargetActivity(targetActivity); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/SequencingRequest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/SequencingRequest.java new file mode 100644 index 00000000..79b75bea --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/SequencingRequest.java @@ -0,0 +1,81 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.sn.api.behavior.common.TraversalDirection; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class SequencingRequest extends Request { + + private Type requestType; + + private TraversalDirection traversalDirection; + + private TraversalDirection previousTraversalDirection; + + private boolean considerChildren; + + public SequencingRequest(@NotNull Type requestType, ActivityTree targetActivityTree, Activity targetActivity) { + super(targetActivityTree, targetActivity); + this.requestType = requestType; + } + + public Type getRequestType() { + return requestType; + } + + public SequencingRequest setRequestType(Type requestType) { + this.requestType = requestType; + return this; + } + + public TraversalDirection getTraversalDirection() { + return traversalDirection; + } + + public SequencingRequest setTraversalDirection(TraversalDirection traversalDirection) { + this.traversalDirection = traversalDirection; + return this; + } + + public TraversalDirection getPreviousTraversalDirection() { + return previousTraversalDirection; + } + + public SequencingRequest setPreviousTraversalDirection(TraversalDirection previousTraversalDirection) { + this.previousTraversalDirection = previousTraversalDirection; + return this; + } + + public boolean isConsiderChildren() { + return considerChildren; + } + + public SequencingRequest setConsiderChildren(boolean considerChildren) { + this.considerChildren = considerChildren; + return this; + } + + @Override + public SequencingRequest setTargetActivityTree(ActivityTree targetActivityTree) { + return (SequencingRequest) super.setTargetActivityTree(targetActivityTree); + } + + @Override + public SequencingRequest setTargetActivity(Activity targetActivity) { + return (SequencingRequest) super.setTargetActivity(targetActivity); + } + + public enum Type { + Start, + ResumeAll, + Continue, + Previous, + Choice, + Retry, + Exit, + Jump + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/TerminationRequest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/TerminationRequest.java new file mode 100644 index 00000000..e2310f2b --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/TerminationRequest.java @@ -0,0 +1,45 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class TerminationRequest extends Request { + + private Type requestType; + + public TerminationRequest(@NotNull Type requestType, ActivityTree targetActivityTree, Activity targetActivity) { + super(targetActivityTree, targetActivity); + this.requestType = requestType; + } + + public Type getRequestType() { + return requestType; + } + + public TerminationRequest setRequestType(Type requestType) { + this.requestType = requestType; + return this; + } + + @Override + public TerminationRequest setTargetActivityTree(ActivityTree targetActivityTree) { + return (TerminationRequest) super.setTargetActivityTree(targetActivityTree); + } + + @Override + public TerminationRequest setTargetActivity(Activity targetActivity) { + return (TerminationRequest) super.setTargetActivity(targetActivity); + } + + public enum Type { + Exit, + ExitParent, + ExitAll, + SuspendAll, + Abandon, + AbandonAll + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/UtilityProcessRequest.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/UtilityProcessRequest.java new file mode 100644 index 00000000..49f9ed0c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/request/UtilityProcessRequest.java @@ -0,0 +1,56 @@ +package com.xboe.module.scorm.sn.api.request; + +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription; +import com.xboe.module.scorm.sn.model.definition.SequencingRuleDescription.ConditionType; +import com.xboe.module.scorm.sn.model.tree.Activity; +import com.xboe.module.scorm.sn.model.tree.ActivityTree; + +public class UtilityProcessRequest extends Request { + + private ConditionType conditionType; + + private String[] ruleActions; + + private SequencingRuleDescription sequencingRuleDescription; + + public UtilityProcessRequest(ActivityTree targetActivityTree, Activity targetActivity) { + super(targetActivityTree, targetActivity); + } + + @Override + public UtilityProcessRequest setTargetActivityTree(ActivityTree targetActivityTree) { + return (UtilityProcessRequest) super.setTargetActivityTree(targetActivityTree); + } + + @Override + public UtilityProcessRequest setTargetActivity(Activity targetActivity) { + return (UtilityProcessRequest) super.setTargetActivity(targetActivity); + } + + public ConditionType getConditionType() { + return conditionType; + } + + public UtilityProcessRequest setConditionType(ConditionType conditionType) { + this.conditionType = conditionType; + return this; + } + + public String[] getRuleActions() { + return ruleActions; + } + + public UtilityProcessRequest setRuleActions(String... ruleActions) { + this.ruleActions = ruleActions; + return this; + } + + public SequencingRuleDescription getSequencingRuleDescription() { + return sequencingRuleDescription; + } + + public UtilityProcessRequest setSequencingRuleDescription(SequencingRuleDescription sequencingRuleDescription) { + this.sequencingRuleDescription = sequencingRuleDescription; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/util/BooleanUtils.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/util/BooleanUtils.java new file mode 100644 index 00000000..ae5de897 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/api/util/BooleanUtils.java @@ -0,0 +1,68 @@ +package com.xboe.module.scorm.sn.api.util; + +public class BooleanUtils { + + public static Boolean negate(Boolean raw) { + if (raw == null) { + return null; + } else { + return !raw; + } + } + + public static Boolean and(Boolean... values) { + if (values.length == 0) { + return null; + } + if (values.length == 1) { + return values[0]; + } + Boolean res = values[0]; + for (int i = 1; i < values.length; i++) { + res = and(res, values[i]); + } + return res; + } + + public static Boolean or(Boolean... values) { + if (values.length == 0) { + return null; + } + if (values.length == 1) { + return values[0]; + } + Boolean res = values[0]; + for (int i = 1; i < values.length; i++) { + res = or(res, values[i]); + } + return res; + } + + private static Boolean and(Boolean one, Boolean two) { + if (one == null && two == null) { + return null; + } + if (one == null || two == null) { + if (one == null) { + return two ? null : false; + } else { + return one ? null : false; + } + } + return one && two; + } + + private static Boolean or(Boolean one, Boolean two) { + if (one == null && two == null) { + return null; + } + if (one == null || two == null) { + if (one == null) { + return two ? true : null; + } else { + return one ? true : null; + } + } + return one || two; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/DecimalWithRange.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/DecimalWithRange.java new file mode 100644 index 00000000..e14ab930 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/DecimalWithRange.java @@ -0,0 +1,55 @@ +package com.xboe.module.scorm.sn.model.datatype; + +import java.math.BigDecimal; + +public class DecimalWithRange implements Comparable<DecimalWithRange> { + + private BigDecimal value; + private final BigDecimal min; + private final BigDecimal max; + private final int scale; + + public DecimalWithRange(double value, double min, double max, int scale) { + this(min, max, scale); + setValue(value); + } + + public DecimalWithRange(double min, double max, int scale) { + this.scale = scale; + this.min = new BigDecimal(min).setScale(scale, BigDecimal.ROUND_HALF_UP); + this.max = new BigDecimal(max).setScale(scale, BigDecimal.ROUND_HALF_UP); + if (this.min.compareTo(this.max) > 0) { + throw new IllegalArgumentException(); + } + } + + public BigDecimal getValue() { + return value; + } + + public void setValue(double value) { + BigDecimal newValue = new BigDecimal(value).setScale(scale, BigDecimal.ROUND_HALF_UP); + if (min.compareTo(newValue) <= 0 && newValue.compareTo(max) <= 0) { + this.value = newValue; + } else { + throw new IllegalArgumentException(); + } + } + + public BigDecimal getMin() { + return min; + } + + public BigDecimal getMax() { + return max; + } + + public int getScale() { + return scale; + } + + @Override + public int compareTo(DecimalWithRange that) { + return this.value.compareTo(that.value); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/NonNegativeInteger.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/NonNegativeInteger.java new file mode 100644 index 00000000..aaf030e4 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/NonNegativeInteger.java @@ -0,0 +1,22 @@ +package com.xboe.module.scorm.sn.model.datatype; + +public class NonNegativeInteger { + + private int value; + + public NonNegativeInteger(int value) { + setValue(value); + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + if (value >= 0) { + this.value = value; + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/Vocabulary.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/Vocabulary.java new file mode 100644 index 00000000..e319c798 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/datatype/Vocabulary.java @@ -0,0 +1,33 @@ +package com.xboe.module.scorm.sn.model.datatype; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class Vocabulary { + + private final Set<String> vocabularyTable; + private String value; + + public Vocabulary(String value, String... vocabulary) { + this.vocabularyTable = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(vocabulary))); + setValue(value); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + if (vocabularyTable.contains(value)) { + this.value = value; + } else { + throw new IllegalArgumentException(); + } + } + + public Set<String> getVocabularyTable() { + return vocabularyTable; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/CompletionThreshold.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/CompletionThreshold.java new file mode 100644 index 00000000..f8d06389 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/CompletionThreshold.java @@ -0,0 +1,35 @@ +package com.xboe.module.scorm.sn.model.definition; + +import com.xboe.module.scorm.sn.model.datatype.DecimalWithRange; + +/** + * @see com.xboe.module.scorm.cam.model.CompletionThreshold + */ +public class CompletionThreshold implements DefinitionElementSet { + + private boolean completedByMeasure; + private final DecimalWithRange minimumProgressMeasure; + private final DecimalWithRange progressWeight; + + public CompletionThreshold() { + completedByMeasure = false; + minimumProgressMeasure = new DecimalWithRange(1, 0, 1, 4); + progressWeight = new DecimalWithRange(1, 0, 1, 4); + } + + public boolean isCompletedByMeasure() { + return completedByMeasure; + } + + public void setCompletedByMeasure(boolean completedByMeasure) { + this.completedByMeasure = completedByMeasure; + } + + public DecimalWithRange getMinimumProgressMeasure() { + return minimumProgressMeasure; + } + + public DecimalWithRange getProgressWeight() { + return progressWeight; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ConstrainChoiceControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ConstrainChoiceControls.java new file mode 100644 index 00000000..b3d86e73 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ConstrainChoiceControls.java @@ -0,0 +1,31 @@ +package com.xboe.module.scorm.sn.model.definition; + +/** + * @see com.xboe.module.scorm.cam.model.ConstrainedChoiceConsiderations + */ +public class ConstrainChoiceControls implements DefinitionElementSet { + + private boolean constrainChoice; + private boolean preventActivation; + + public ConstrainChoiceControls() { + constrainChoice = false; + preventActivation = false; + } + + public boolean isConstrainChoice() { + return constrainChoice; + } + + public void setConstrainChoice(boolean constrainChoice) { + this.constrainChoice = constrainChoice; + } + + public boolean isPreventActivation() { + return preventActivation; + } + + public void setPreventActivation(boolean preventActivation) { + this.preventActivation = preventActivation; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/DefinitionElementSet.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/DefinitionElementSet.java new file mode 100644 index 00000000..b102999a --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/DefinitionElementSet.java @@ -0,0 +1,9 @@ +package com.xboe.module.scorm.sn.model.definition; + +public interface DefinitionElementSet { + + default boolean isInherited() { + return false; + } + +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/DeliveryControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/DeliveryControls.java new file mode 100644 index 00000000..6a0382c8 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/DeliveryControls.java @@ -0,0 +1,41 @@ +package com.xboe.module.scorm.sn.model.definition; + +/** + * @see com.xboe.module.scorm.cam.model.DeliveryControls + */ +public class DeliveryControls implements DefinitionElementSet { + + private boolean tracked; + private boolean completionSetByContent; + private boolean objectiveSetByContent; + + public DeliveryControls() { + tracked = true; + completionSetByContent = false; + objectiveSetByContent = false; + } + + public boolean isTracked() { + return tracked; + } + + public void setTracked(boolean tracked) { + this.tracked = tracked; + } + + public boolean isCompletionSetByContent() { + return completionSetByContent; + } + + public void setCompletionSetByContent(boolean completionSetByContent) { + this.completionSetByContent = completionSetByContent; + } + + public boolean isObjectiveSetByContent() { + return objectiveSetByContent; + } + + public void setObjectiveSetByContent(boolean objectiveSetByContent) { + this.objectiveSetByContent = objectiveSetByContent; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/HideLmsUIControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/HideLmsUIControls.java new file mode 100644 index 00000000..f6c8d84e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/HideLmsUIControls.java @@ -0,0 +1,85 @@ +package com.xboe.module.scorm.sn.model.definition; + +public class HideLmsUIControls { + + private boolean hidePrevious; + private boolean hideContinue; + private boolean hideExit; + private boolean hideExitAll; + private boolean hideAbandon; + private boolean hideAbandonAll; + private boolean hideSuspendAll; + + public HideLmsUIControls() { + hidePrevious = false; + hideContinue = false; + hideExit = false; + hideExitAll = false; + hideAbandon = false; + hideAbandonAll = false; + hideSuspendAll = false; + } + + public boolean isHidePrevious() { + return hidePrevious; + } + + public HideLmsUIControls setHidePrevious(boolean hidePrevious) { + this.hidePrevious = hidePrevious; + return this; + } + + public boolean isHideContinue() { + return hideContinue; + } + + public HideLmsUIControls setHideContinue(boolean hideContinue) { + this.hideContinue = hideContinue; + return this; + } + + public boolean isHideExit() { + return hideExit; + } + + public HideLmsUIControls setHideExit(boolean hideExit) { + this.hideExit = hideExit; + return this; + } + + public boolean isHideExitAll() { + return hideExitAll; + } + + public HideLmsUIControls setHideExitAll(boolean hideExitAll) { + this.hideExitAll = hideExitAll; + return this; + } + + public boolean isHideAbandon() { + return hideAbandon; + } + + public HideLmsUIControls setHideAbandon(boolean hideAbandon) { + this.hideAbandon = hideAbandon; + return this; + } + + public boolean isHideAbandonAll() { + return hideAbandonAll; + } + + public HideLmsUIControls setHideAbandonAll(boolean hideAbandonAll) { + this.hideAbandonAll = hideAbandonAll; + return this; + } + + public boolean isHideSuspendAll() { + return hideSuspendAll; + } + + public HideLmsUIControls setHideSuspendAll(boolean hideSuspendAll) { + this.hideSuspendAll = hideSuspendAll; + return this; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/LimitConditions.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/LimitConditions.java new file mode 100644 index 00000000..3e52fea3 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/LimitConditions.java @@ -0,0 +1,51 @@ +package com.xboe.module.scorm.sn.model.definition; + +import java.time.Duration; + +import com.xboe.module.scorm.sn.model.datatype.NonNegativeInteger; + +/** + * @see com.xboe.module.scorm.cam.model.LimitConditions + */ +public class LimitConditions implements DefinitionElementSet { + + private boolean attemptControl; + private final NonNegativeInteger attemptLimit; + private boolean attemptAbsoluteDurationControl; + private Duration attemptAbsoluteDurationLimit; + + public LimitConditions() { + attemptControl = false; + attemptLimit = new NonNegativeInteger(0); + attemptAbsoluteDurationControl = false; + attemptAbsoluteDurationLimit = Duration.ZERO; + } + + public boolean isAttemptControl() { + return attemptControl; + } + + public void setAttemptControl(boolean attemptControl) { + this.attemptControl = attemptControl; + } + + public NonNegativeInteger getAttemptLimit() { + return attemptLimit; + } + + public boolean isAttemptAbsoluteDurationControl() { + return attemptAbsoluteDurationControl; + } + + public void setAttemptAbsoluteDurationControl(boolean attemptAbsoluteDurationControl) { + this.attemptAbsoluteDurationControl = attemptAbsoluteDurationControl; + } + + public Duration getAttemptAbsoluteDurationLimit() { + return attemptAbsoluteDurationLimit; + } + + public void setAttemptAbsoluteDurationLimit(Duration attemptAbsoluteDurationLimit) { + this.attemptAbsoluteDurationLimit = attemptAbsoluteDurationLimit; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ObjectiveDescription.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ObjectiveDescription.java new file mode 100644 index 00000000..f9aa10e6 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ObjectiveDescription.java @@ -0,0 +1,67 @@ +package com.xboe.module.scorm.sn.model.definition; + +import java.util.ArrayList; +import java.util.List; + +import com.xboe.module.scorm.sn.model.datatype.DecimalWithRange; +import com.xboe.module.scorm.sn.model.tracking.ObjectiveProgressInformation; +import com.xboe.module.scorm.sn.model.tree.Activity; + +/** + * @see com.xboe.module.scorm.cam.model.Objective + */ +public class ObjectiveDescription implements DefinitionElementSet { + + private String objectiveID; + private boolean objectiveSatisfiedByMeasure; + private final DecimalWithRange objectiveMinimumSatisfiedNormalizedMeasure; + private final boolean objectiveContributesToRollup; + private final List<ObjectiveMap> objectiveMaps; + private final ObjectiveProgressInformation objectiveProgressInformation; + private final Activity context; + + public ObjectiveDescription(boolean isPrimaryObjective, Activity context) { + objectiveSatisfiedByMeasure = false; + objectiveMinimumSatisfiedNormalizedMeasure = new DecimalWithRange(1, -1, 1, 4); + objectiveContributesToRollup = isPrimaryObjective; + objectiveMaps = new ArrayList<>(); + objectiveProgressInformation = new ObjectiveProgressInformation(this); + this.context = context; + } + + public String getObjectiveID() { + return objectiveID; + } + + public void setObjectiveID(String objectiveID) { + this.objectiveID = objectiveID; + } + + public boolean isObjectiveSatisfiedByMeasure() { + return objectiveSatisfiedByMeasure; + } + + public void setObjectiveSatisfiedByMeasure(boolean objectiveSatisfiedByMeasure) { + this.objectiveSatisfiedByMeasure = objectiveSatisfiedByMeasure; + } + + public DecimalWithRange getObjectiveMinimumSatisfiedNormalizedMeasure() { + return objectiveMinimumSatisfiedNormalizedMeasure; + } + + public boolean isObjectiveContributesToRollup() { + return objectiveContributesToRollup; + } + + public List<ObjectiveMap> getObjectiveMaps() { + return objectiveMaps; + } + + public ObjectiveProgressInformation getObjectiveProgressInformation() { + return objectiveProgressInformation; + } + + public Activity getContext() { + return context; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ObjectiveMap.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ObjectiveMap.java new file mode 100644 index 00000000..4e2e3311 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/ObjectiveMap.java @@ -0,0 +1,160 @@ +package com.xboe.module.scorm.sn.model.definition; + +public class ObjectiveMap { + + private final String activityObjectiveID; + private final String targetObjectiveID; + private boolean readObjectiveSatisfiedStatus; + private boolean writeObjectiveSatisfiedStatus; + private boolean readObjectiveNormalizedMeasure; + private boolean writeObjectiveNormalizedMeasure; + private boolean readRawScore; + private boolean writeRawScore; + private boolean readMinScore; + private boolean writeMinScore; + private boolean readMaxScore; + private boolean writeMaxScore; + private boolean readCompletionStatus; + private boolean writeCompletionStatus; + private boolean readProgressMeasure; + private boolean writeProgressMeasure; + + public ObjectiveMap(String activityObjectiveID, String targetObjectiveID) { + this.activityObjectiveID = activityObjectiveID; + this.targetObjectiveID = targetObjectiveID; + readObjectiveSatisfiedStatus = true; + writeObjectiveSatisfiedStatus = false; + readObjectiveNormalizedMeasure = true; + writeObjectiveNormalizedMeasure = false; + readRawScore = true; + writeRawScore = false; + readMinScore = true; + writeMinScore = false; + readMaxScore = true; + writeMaxScore = false; + readCompletionStatus = true; + writeCompletionStatus = false; + readProgressMeasure = true; + writeProgressMeasure = false; + } + + public String getActivityObjectiveID() { + return activityObjectiveID; + } + + public String getTargetObjectiveID() { + return targetObjectiveID; + } + + public boolean isReadObjectiveSatisfiedStatus() { + return readObjectiveSatisfiedStatus; + } + + public void setReadObjectiveSatisfiedStatus(boolean readObjectiveSatisfiedStatus) { + this.readObjectiveSatisfiedStatus = readObjectiveSatisfiedStatus; + } + + public boolean isWriteObjectiveSatisfiedStatus() { + return writeObjectiveSatisfiedStatus; + } + + public void setWriteObjectiveSatisfiedStatus(boolean writeObjectiveSatisfiedStatus) { + this.writeObjectiveSatisfiedStatus = writeObjectiveSatisfiedStatus; + } + + public boolean isReadObjectiveNormalizedMeasure() { + return readObjectiveNormalizedMeasure; + } + + public void setReadObjectiveNormalizedMeasure(boolean readObjectiveNormalizedMeasure) { + this.readObjectiveNormalizedMeasure = readObjectiveNormalizedMeasure; + } + + public boolean isWriteObjectiveNormalizedMeasure() { + return writeObjectiveNormalizedMeasure; + } + + public void setWriteObjectiveNormalizedMeasure(boolean writeObjectiveNormalizedMeasure) { + this.writeObjectiveNormalizedMeasure = writeObjectiveNormalizedMeasure; + } + + public boolean isReadRawScore() { + return readRawScore; + } + + public void setReadRawScore(boolean readRawScore) { + this.readRawScore = readRawScore; + } + + public boolean isWriteRawScore() { + return writeRawScore; + } + + public void setWriteRawScore(boolean writeRawScore) { + this.writeRawScore = writeRawScore; + } + + public boolean isReadMinScore() { + return readMinScore; + } + + public void setReadMinScore(boolean readMinScore) { + this.readMinScore = readMinScore; + } + + public boolean isWriteMinScore() { + return writeMinScore; + } + + public void setWriteMinScore(boolean writeMinScore) { + this.writeMinScore = writeMinScore; + } + + public boolean isReadMaxScore() { + return readMaxScore; + } + + public void setReadMaxScore(boolean readMaxScore) { + this.readMaxScore = readMaxScore; + } + + public boolean isWriteMaxScore() { + return writeMaxScore; + } + + public void setWriteMaxScore(boolean writeMaxScore) { + this.writeMaxScore = writeMaxScore; + } + + public boolean isReadCompletionStatus() { + return readCompletionStatus; + } + + public void setReadCompletionStatus(boolean readCompletionStatus) { + this.readCompletionStatus = readCompletionStatus; + } + + public boolean isWriteCompletionStatus() { + return writeCompletionStatus; + } + + public void setWriteCompletionStatus(boolean writeCompletionStatus) { + this.writeCompletionStatus = writeCompletionStatus; + } + + public boolean isReadProgressMeasure() { + return readProgressMeasure; + } + + public void setReadProgressMeasure(boolean readProgressMeasure) { + this.readProgressMeasure = readProgressMeasure; + } + + public boolean isWriteProgressMeasure() { + return writeProgressMeasure; + } + + public void setWriteProgressMeasure(boolean writeProgressMeasure) { + this.writeProgressMeasure = writeProgressMeasure; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RandomizationControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RandomizationControls.java new file mode 100644 index 00000000..377f44c1 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RandomizationControls.java @@ -0,0 +1,29 @@ +package com.xboe.module.scorm.sn.model.definition; + +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; + +/** + * @see com.xboe.module.scorm.cam.model.RandomizationControls + */ +public class RandomizationControls implements DefinitionElementSet { + + private final Vocabulary randomizationTiming; + private boolean randomizeChildren; + + public RandomizationControls() { + randomizationTiming = new Vocabulary("Never", "Never", "Once", "On Each New Attempt"); + randomizeChildren = false; + } + + public Vocabulary getRandomizationTiming() { + return randomizationTiming; + } + + public boolean isRandomizeChildren() { + return randomizeChildren; + } + + public void setRandomizeChildren(boolean randomizeChildren) { + this.randomizeChildren = randomizeChildren; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupCondition.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupCondition.java new file mode 100644 index 00000000..eaf593b6 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupCondition.java @@ -0,0 +1,27 @@ +package com.xboe.module.scorm.sn.model.definition; + +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; + +/** + * @see com.xboe.module.scorm.cam.model.RollupCondition + */ +public class RollupCondition { + + private final Vocabulary rollupCondition; + private final Vocabulary operator; + + public RollupCondition() { + rollupCondition = new Vocabulary("Never", "Satisfied", "Objective Status Known", + "Objective Measure Known", "Completed", "Activity Progress Known", "Attempted", "Attempt Limit Exceeded", + "Never"); + operator = new Vocabulary("NO-OP", "NO-OP", "Not"); + } + + public Vocabulary getRollupCondition() { + return rollupCondition; + } + + public Vocabulary getOperator() { + return operator; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupConsiderationControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupConsiderationControls.java new file mode 100644 index 00000000..5d559a43 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupConsiderationControls.java @@ -0,0 +1,51 @@ +package com.xboe.module.scorm.sn.model.definition; + +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; + +/** + * @see com.xboe.module.scorm.cam.model.RollupConsiderations + */ +public class RollupConsiderationControls implements DefinitionElementSet { + + private boolean measureSatisfactionIfActive; + private final Vocabulary requiredForSatisfied; + private final Vocabulary requiredForNotSatisfied; + private final Vocabulary requiredForCompleted; + private final Vocabulary requiredForIncomplete; + + public RollupConsiderationControls() { + measureSatisfactionIfActive = true; + requiredForSatisfied = new Vocabulary("always", + "always", "ifNotSuspended", "ifAttempted", "ifNotSkipped"); + requiredForNotSatisfied = new Vocabulary("always", + "always", "ifNotSuspended", "ifAttempted", "ifNotSkipped"); + requiredForCompleted = new Vocabulary("always", + "always", "ifNotSuspended", "ifAttempted", "ifNotSkipped"); + requiredForIncomplete = new Vocabulary("always", + "always", "ifNotSuspended", "ifAttempted", "ifNotSkipped"); + } + + public boolean isMeasureSatisfactionIfActive() { + return measureSatisfactionIfActive; + } + + public void setMeasureSatisfactionIfActive(boolean measureSatisfactionIfActive) { + this.measureSatisfactionIfActive = measureSatisfactionIfActive; + } + + public Vocabulary getRequiredForSatisfied() { + return requiredForSatisfied; + } + + public Vocabulary getRequiredForNotSatisfied() { + return requiredForNotSatisfied; + } + + public Vocabulary getRequiredForCompleted() { + return requiredForCompleted; + } + + public Vocabulary getRequiredForIncomplete() { + return requiredForIncomplete; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupControls.java new file mode 100644 index 00000000..f2ec9dbd --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupControls.java @@ -0,0 +1,39 @@ +package com.xboe.module.scorm.sn.model.definition; + +import com.xboe.module.scorm.sn.model.datatype.DecimalWithRange; + +/** + * @see com.xboe.module.scorm.cam.model.RollupRules + */ +public class RollupControls implements DefinitionElementSet { + + private boolean rollupObjectiveSatisfied; + private final DecimalWithRange rollupObjectiveMeasureWeight; + private boolean rollupProgressCompletion; + + public RollupControls() { + rollupObjectiveSatisfied = true; + rollupObjectiveMeasureWeight = new DecimalWithRange(1, 0, 1, 4); + rollupProgressCompletion = true; + } + + public boolean isRollupObjectiveSatisfied() { + return rollupObjectiveSatisfied; + } + + public void setRollupObjectiveSatisfied(boolean rollupObjectiveSatisfied) { + this.rollupObjectiveSatisfied = rollupObjectiveSatisfied; + } + + public DecimalWithRange getRollupObjectiveMeasureWeight() { + return rollupObjectiveMeasureWeight; + } + + public boolean isRollupProgressCompletion() { + return rollupProgressCompletion; + } + + public void setRollupProgressCompletion(boolean rollupProgressCompletion) { + this.rollupProgressCompletion = rollupProgressCompletion; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupRuleDescription.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupRuleDescription.java new file mode 100644 index 00000000..b20a02d5 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RollupRuleDescription.java @@ -0,0 +1,54 @@ +package com.xboe.module.scorm.sn.model.definition; + +import java.util.ArrayList; +import java.util.List; + +import com.xboe.module.scorm.sn.model.datatype.DecimalWithRange; +import com.xboe.module.scorm.sn.model.datatype.NonNegativeInteger; +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; + +/** + * @see com.xboe.module.scorm.cam.model.RollupRule + */ +public class RollupRuleDescription implements DefinitionElementSet { + + private final Vocabulary conditionCombination; + private final List<RollupCondition> rollupConditions; + private final Vocabulary childActivitySet; + private final NonNegativeInteger rollupMinimumCount; + private final DecimalWithRange rollupMinimumPercent; + private final Vocabulary rollupAction; + + public RollupRuleDescription() { + conditionCombination = new Vocabulary("Any", "All", "Any"); + rollupConditions = new ArrayList<>(); + childActivitySet = new Vocabulary("All", "All", "Any", "None", "At Least Count", "At Least Percent"); + rollupMinimumCount = new NonNegativeInteger(0); + rollupMinimumPercent = new DecimalWithRange(0, 0, 1, 4); + rollupAction = new Vocabulary("Satisfied", "Satisfied", "Not Satisfied", "Completed", "Incomplete"); + } + + public Vocabulary getConditionCombination() { + return conditionCombination; + } + + public List<RollupCondition> getRollupConditions() { + return rollupConditions; + } + + public Vocabulary getChildActivitySet() { + return childActivitySet; + } + + public NonNegativeInteger getRollupMinimumCount() { + return rollupMinimumCount; + } + + public DecimalWithRange getRollupMinimumPercent() { + return rollupMinimumPercent; + } + + public Vocabulary getRollupAction() { + return rollupAction; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RuleCondition.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RuleCondition.java new file mode 100644 index 00000000..4e2b638e --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/RuleCondition.java @@ -0,0 +1,43 @@ +package com.xboe.module.scorm.sn.model.definition; + +import com.xboe.module.scorm.sn.model.datatype.DecimalWithRange; +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; + +/** + * @see com.xboe.module.scorm.cam.model.RuleCondition + */ +public class RuleCondition { + + private final Vocabulary ruleCondition; + private String referencedObjective; + private final DecimalWithRange measureThreshold; + private final Vocabulary operator; + + public RuleCondition() { + ruleCondition = new Vocabulary("Always", "Satisfied", "Objective Status Known", + "Objective Measure Known", "Objective Measure Greater Than", "Objective Measure Less Than", + "Completed", "Activity Progress Known", "Attempted", "Attempt Limit Exceeded", "Always"); + measureThreshold = new DecimalWithRange(0, -1, 1, 4); + operator = new Vocabulary("NO-OP", "NO-OP", "Not"); + } + + public Vocabulary getRuleCondition() { + return ruleCondition; + } + + public String getReferencedObjective() { + return referencedObjective; + } + + public void setReferencedObjective(String referencedObjective) { + this.referencedObjective = referencedObjective; + } + + public DecimalWithRange getMeasureThreshold() { + return measureThreshold; + } + + public Vocabulary getOperator() { + return operator; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SelectionControls.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SelectionControls.java new file mode 100644 index 00000000..2409e58c --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SelectionControls.java @@ -0,0 +1,36 @@ +package com.xboe.module.scorm.sn.model.definition; + +import com.xboe.module.scorm.sn.model.datatype.NonNegativeInteger; +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; + +/** + * @see com.xboe.module.scorm.cam.model.RandomizationControls + */ +public class SelectionControls implements DefinitionElementSet { + + private final Vocabulary selectionTiming; + private boolean selectionCountStatus; + private final NonNegativeInteger selectionCount; + + public SelectionControls() { + selectionTiming = new Vocabulary("Never", "Never", "Once", "On Each New Attempt"); + selectionCountStatus = false; + selectionCount = new NonNegativeInteger(0); + } + + public Vocabulary getSelectionTiming() { + return selectionTiming; + } + + public boolean isSelectionCountStatus() { + return selectionCountStatus; + } + + public void setSelectionCountStatus(boolean selectionCountStatus) { + this.selectionCountStatus = selectionCountStatus; + } + + public NonNegativeInteger getSelectionCount() { + return selectionCount; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingControlMode.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingControlMode.java new file mode 100644 index 00000000..fabf57dd --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingControlMode.java @@ -0,0 +1,71 @@ +package com.xboe.module.scorm.sn.model.definition; + +/** + * @see com.xboe.module.scorm.cam.model.ControlMode + */ +public class SequencingControlMode implements DefinitionElementSet { + + private boolean sequencingControlChoice; + private boolean sequencingControlChoiceExit; + private boolean sequencingControlFlow; + private boolean sequencingControlForwardOnly; + private boolean useCurrentAttemptObjectiveInformation; + private boolean useCurrentAttemptProgressInformation; + + public SequencingControlMode() { + sequencingControlChoice = true; + sequencingControlChoiceExit = true; + sequencingControlFlow = false; + sequencingControlForwardOnly = false; + useCurrentAttemptObjectiveInformation = true; + useCurrentAttemptProgressInformation = true; + } + + public boolean isSequencingControlChoice() { + return sequencingControlChoice; + } + + public void setSequencingControlChoice(boolean sequencingControlChoice) { + this.sequencingControlChoice = sequencingControlChoice; + } + + public boolean isSequencingControlChoiceExit() { + return sequencingControlChoiceExit; + } + + public void setSequencingControlChoiceExit(boolean sequencingControlChoiceExit) { + this.sequencingControlChoiceExit = sequencingControlChoiceExit; + } + + public boolean isSequencingControlFlow() { + return sequencingControlFlow; + } + + public void setSequencingControlFlow(boolean sequencingControlFlow) { + this.sequencingControlFlow = sequencingControlFlow; + } + + public boolean isSequencingControlForwardOnly() { + return sequencingControlForwardOnly; + } + + public void setSequencingControlForwardOnly(boolean sequencingControlForwardOnly) { + this.sequencingControlForwardOnly = sequencingControlForwardOnly; + } + + public boolean isUseCurrentAttemptObjectiveInformation() { + return useCurrentAttemptObjectiveInformation; + } + + public void setUseCurrentAttemptObjectiveInformation(boolean useCurrentAttemptObjectiveInformation) { + this.useCurrentAttemptObjectiveInformation = useCurrentAttemptObjectiveInformation; + } + + public boolean isUseCurrentAttemptProgressInformation() { + return useCurrentAttemptProgressInformation; + } + + public void setUseCurrentAttemptProgressInformation(boolean useCurrentAttemptProgressInformation) { + this.useCurrentAttemptProgressInformation = useCurrentAttemptProgressInformation; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingDefinition.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingDefinition.java new file mode 100644 index 00000000..32b76d23 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingDefinition.java @@ -0,0 +1,110 @@ +package com.xboe.module.scorm.sn.model.definition; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.xboe.module.scorm.sn.model.tree.Activity; + +public class SequencingDefinition { + + private final Activity context; + private final SequencingControlMode sequencingControlMode; + private final ConstrainChoiceControls constrainChoiceControls; + private final List<SequencingRuleDescription> sequencingRuleDescriptions; + private final LimitConditions limitConditions; + private final List<RollupRuleDescription> rollupRuleDescriptions; + private final RollupControls rollupControls; + private final RollupConsiderationControls rollupConsiderationControls; + private final List<ObjectiveDescription> objectiveDescriptions; + private final SelectionControls selectionControls; + private final RandomizationControls randomizationControls; + private final DeliveryControls deliveryControls; + private final CompletionThreshold completionThreshold; + + public SequencingDefinition(Activity context) { + this.context = context; + sequencingControlMode = new SequencingControlMode(); + constrainChoiceControls = new ConstrainChoiceControls(); + sequencingRuleDescriptions = new ArrayList<>(); + limitConditions = new LimitConditions(); + rollupRuleDescriptions = new ArrayList<>(); + rollupControls = new RollupControls(); + rollupConsiderationControls = new RollupConsiderationControls(); + objectiveDescriptions = new ArrayList<>(); + selectionControls = new SelectionControls(); + randomizationControls = new RandomizationControls(); + deliveryControls = new DeliveryControls(); + completionThreshold = new CompletionThreshold(); + } + + public Activity getContext() { + return context; + } + + public SequencingControlMode getSequencingControlMode() { + return sequencingControlMode; + } + + public ConstrainChoiceControls getConstrainChoiceControls() { + return constrainChoiceControls; + } + + public List<SequencingRuleDescription> getSequencingRuleDescriptions() { + return sequencingRuleDescriptions; + } + + public LimitConditions getLimitConditions() { + return limitConditions; + } + + public List<RollupRuleDescription> getRollupRuleDescriptions() { + return rollupRuleDescriptions; + } + + public RollupControls getRollupControls() { + return rollupControls; + } + + public RollupConsiderationControls getRollupConsiderationControls() { + return rollupConsiderationControls; + } + + public List<ObjectiveDescription> getObjectiveDescriptions() { + return objectiveDescriptions; + } + + public SelectionControls getSelectionControls() { + return selectionControls; + } + + public RandomizationControls getRandomizationControls() { + return randomizationControls; + } + + public DeliveryControls getDeliveryControls() { + return deliveryControls; + } + + public CompletionThreshold getCompletionThreshold() { + return completionThreshold; + } + + public ObjectiveDescription findObjectiveDescriptionByID(String objectiveID) { + for (ObjectiveDescription objectiveDescription : objectiveDescriptions) { + if (Objects.equals(objectiveID, objectiveDescription.getObjectiveID())) { + return objectiveDescription; + } + } + return null; + } + + public ObjectiveDescription getPrimaryObjectiveDescription() { + for (ObjectiveDescription objectiveDescription : objectiveDescriptions) { + if (objectiveDescription.isObjectiveContributesToRollup()) { + return objectiveDescription; + } + } + return null; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingRuleDescription.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingRuleDescription.java new file mode 100644 index 00000000..2ae1bf94 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/definition/SequencingRuleDescription.java @@ -0,0 +1,59 @@ +package com.xboe.module.scorm.sn.model.definition; + +import java.util.ArrayList; +import java.util.List; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.sn.model.datatype.Vocabulary; + +/** + * @see com.xboe.module.scorm.cam.model.RuleConditions + * @see com.xboe.module.scorm.cam.model.ConditionRule + */ +public class SequencingRuleDescription implements DefinitionElementSet { + + private final ConditionType conditionType; + private final Vocabulary conditionCombination; + private final List<RuleCondition> ruleConditions; + private final Vocabulary ruleAction; + + public SequencingRuleDescription(@NotNull ConditionType conditionType) { + this.conditionType = conditionType; + conditionCombination = new Vocabulary("All", "All", "Any"); + ruleConditions = new ArrayList<>(); + ruleAction = new Vocabulary("Ignore", conditionType.ruleActionVocabularyTable); + } + + public ConditionType getConditionType() { + return conditionType; + } + + public Vocabulary getConditionCombination() { + return conditionCombination; + } + + public List<RuleCondition> getRuleConditions() { + return ruleConditions; + } + + public Vocabulary getRuleAction() { + return ruleAction; + } + + public enum ConditionType { + PRECONDITION("Skip", "Disabled", "Hidden from Choice", "Stop Forward Traversal", "Ignore"), + POSTCONDITION("Exit Parent", "Exit All", "Retry", "Retry All", "Continue", "Previous", "Ignore"), + EXITCONDITION("Exit", "Ignore"); + + ConditionType(String... ruleActionVocabularyTable) { + this.ruleActionVocabularyTable = ruleActionVocabularyTable; + } + + private final String[] ruleActionVocabularyTable; + + public String[] getRuleActionVocabularyTable() { + return ruleActionVocabularyTable; + } + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/global/GlobalObjectiveDescription.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/global/GlobalObjectiveDescription.java new file mode 100644 index 00000000..041f629f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/global/GlobalObjectiveDescription.java @@ -0,0 +1,36 @@ +package com.xboe.module.scorm.sn.model.global; + +import com.xboe.module.scorm.sn.model.tracking.ObjectiveProgressInformation; + +public class GlobalObjectiveDescription { + +// private boolean objectiveSatisfiedStatus; +// private boolean objectiveNormalizedMeasure; +// private boolean rawScore; +// private boolean minScore; +// private boolean maxScore; +// private boolean completionStatus; +// private boolean progressMeasure; + + private final String objectiveID; + private final ObjectiveProgressInformation objectiveProgressInformation; +// private final AttemptProgressInformation attemptProgressInformation; + + public GlobalObjectiveDescription(String objectiveID) { + this.objectiveID = objectiveID; + this.objectiveProgressInformation = new ObjectiveProgressInformation(this); +// this.attemptProgressInformation = new AttemptProgressInformation(this); + } + + public String getObjectiveID() { + return objectiveID; + } + + public ObjectiveProgressInformation getObjectiveProgressInformation() { + return objectiveProgressInformation; + } + +// public AttemptProgressInformation getAttemptProgressInformation() { +// return attemptProgressInformation; +// } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ActivityProgressInformation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ActivityProgressInformation.java new file mode 100644 index 00000000..ae83bd21 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ActivityProgressInformation.java @@ -0,0 +1,61 @@ +package com.xboe.module.scorm.sn.model.tracking; + +import java.time.Duration; + +import com.xboe.module.scorm.sn.model.datatype.NonNegativeInteger; +import com.xboe.module.scorm.sn.model.tree.Activity; + +/** + * Each tracked activity has one set of tracking status information that span all attempts on that activity. + */ +public class ActivityProgressInformation { + + private final Activity context; + private boolean activityProgressStatus; + private Duration activityAbsoluteDuration; + private Duration activityExperiencedDuration; + private final NonNegativeInteger activityAttemptCount; + + public ActivityProgressInformation(Activity context) { + this.context = context; + activityProgressStatus = false; + activityAbsoluteDuration = Duration.ZERO; + activityExperiencedDuration = Duration.ZERO; + activityAttemptCount = new NonNegativeInteger(0); + } + + public Activity getContext() { + return context; + } + + public boolean isActivityProgressStatus() { + return activityProgressStatus; + } + + public ActivityProgressInformation setActivityProgressStatus(boolean activityProgressStatus) { + this.activityProgressStatus = activityProgressStatus; + return this; + } + + public Duration getActivityAbsoluteDuration() { + return activityAbsoluteDuration; + } + + public ActivityProgressInformation setActivityAbsoluteDuration(Duration activityAbsoluteDuration) { + this.activityAbsoluteDuration = activityAbsoluteDuration; + return this; + } + + public Duration getActivityExperiencedDuration() { + return activityExperiencedDuration; + } + + public ActivityProgressInformation setActivityExperiencedDuration(Duration activityExperiencedDuration) { + this.activityExperiencedDuration = activityExperiencedDuration; + return this; + } + + public NonNegativeInteger getActivityAttemptCount() { + return activityAttemptCount; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ActivityStateInformation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ActivityStateInformation.java new file mode 100644 index 00000000..9514f6ef --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ActivityStateInformation.java @@ -0,0 +1,43 @@ +package com.xboe.module.scorm.sn.model.tracking; + +import java.util.ArrayList; +import java.util.List; + +import com.xboe.module.scorm.sn.model.tree.Activity; + +/** + * For each activity in the activity tree on a per learner basis. + * This information exists for each activity regardless if the activity is tracked out. + */ +public class ActivityStateInformation { + + private boolean activityIsActive; + private boolean activityIsSuspended; + private final List<Activity> availableChildren; + + public ActivityStateInformation() { + activityIsActive = false; + activityIsSuspended = false; + availableChildren = new ArrayList<>(); + } + + public boolean isActivityIsActive() { + return activityIsActive; + } + + public void setActivityIsActive(boolean activityIsActive) { + this.activityIsActive = activityIsActive; + } + + public boolean isActivityIsSuspended() { + return activityIsSuspended; + } + + public void setActivityIsSuspended(boolean activityIsSuspended) { + this.activityIsSuspended = activityIsSuspended; + } + + public List<Activity> getAvailableChildren() { + return availableChildren; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/AttemptProgressInformation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/AttemptProgressInformation.java new file mode 100644 index 00000000..870f9786 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/AttemptProgressInformation.java @@ -0,0 +1,86 @@ +package com.xboe.module.scorm.sn.model.tracking; + +import java.time.Duration; + +import com.xboe.module.scorm.sn.model.datatype.DecimalWithRange; + +/** + * For each attempt on a tracked activity, a learner gets one set of attempt progress information + */ +public class AttemptProgressInformation { + + private final Object context; + private boolean attemptProgressStatus; + private boolean attemptCompletionStatus; + private boolean attemptCompletionAmountStatus; + private final DecimalWithRange attemptCompletionAmount; + private Duration attemptAbsoluteDuration; + private Duration attemptExperiencedDuration; + + public AttemptProgressInformation(Object context) { + this.context = context; + attemptProgressStatus = false; + attemptCompletionStatus = false; + attemptCompletionAmountStatus = false; + attemptCompletionAmount = new DecimalWithRange(0, 0, 1, 4); + attemptAbsoluteDuration = Duration.ZERO; + attemptExperiencedDuration = Duration.ZERO; + } + + public void reinit() { + attemptProgressStatus = false; + attemptCompletionStatus = false; + attemptCompletionAmountStatus = false; + attemptCompletionAmount.setValue(0); + attemptAbsoluteDuration = Duration.ZERO; + attemptExperiencedDuration = Duration.ZERO; + } + + public Object getContext() { + return context; + } + + public boolean isAttemptProgressStatus() { + return attemptProgressStatus; + } + + public void setAttemptProgressStatus(boolean attemptProgressStatus) { + this.attemptProgressStatus = attemptProgressStatus; + } + + public boolean isAttemptCompletionStatus() { + return attemptCompletionStatus; + } + + public void setAttemptCompletionStatus(boolean attemptCompletionStatus) { + this.attemptCompletionStatus = attemptCompletionStatus; + } + + public boolean isAttemptCompletionAmountStatus() { + return attemptCompletionAmountStatus; + } + + public void setAttemptCompletionAmountStatus(boolean attemptCompletionAmountStatus) { + this.attemptCompletionAmountStatus = attemptCompletionAmountStatus; + } + + public DecimalWithRange getAttemptCompletionAmount() { + return attemptCompletionAmount; + } + + public Duration getAttemptAbsoluteDuration() { + return attemptAbsoluteDuration; + } + + public void setAttemptAbsoluteDuration(Duration attemptAbsoluteDuration) { + this.attemptAbsoluteDuration = attemptAbsoluteDuration; + } + + public Duration getAttemptExperiencedDuration() { + return attemptExperiencedDuration; + } + + public void setAttemptExperiencedDuration(Duration attemptExperiencedDuration) { + this.attemptExperiencedDuration = attemptExperiencedDuration; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/GlobalStateInformation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/GlobalStateInformation.java new file mode 100644 index 00000000..e9941f20 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/GlobalStateInformation.java @@ -0,0 +1,31 @@ +package com.xboe.module.scorm.sn.model.tracking; + +import com.xboe.module.scorm.sn.model.tree.Activity; + +/** + * for an activity tree + */ +public class GlobalStateInformation { + + private Activity currentActivity; + private Activity suspendedActivity; + + public GlobalStateInformation() { + } + + public Activity getCurrentActivity() { + return currentActivity; + } + + public void setCurrentActivity(Activity currentActivity) { + this.currentActivity = currentActivity; + } + + public Activity getSuspendedActivity() { + return suspendedActivity; + } + + public void setSuspendedActivity(Activity suspendedActivity) { + this.suspendedActivity = suspendedActivity; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ObjectiveProgressInformation.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ObjectiveProgressInformation.java new file mode 100644 index 00000000..5eebd928 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tracking/ObjectiveProgressInformation.java @@ -0,0 +1,315 @@ +package com.xboe.module.scorm.sn.model.tracking; + +import com.xboe.module.scorm.sn.model.datatype.DecimalWithRange; +import com.xboe.module.scorm.sn.model.definition.ObjectiveDescription; +import com.xboe.module.scorm.sn.model.definition.ObjectiveMap; +import com.xboe.module.scorm.sn.model.global.GlobalObjectiveDescription; + +/** + * For each attempt on an activity, a learner gets one set of objective progress information for + * each objective associated with the activity. + */ +public class ObjectiveProgressInformation { + + private final Object context; + + private boolean objectiveProgressStatus; + private boolean objectiveSatisfiedStatus; + + private boolean objectiveMeasureStatus; + private final DecimalWithRange objectiveNormalizedMeasure; + + private boolean objectiveCompletionProgressStatus; + private boolean objectiveCompletionStatus; + + private boolean objectiveCompletionAmountStatus; + private final DecimalWithRange objectiveCompletionAmount; + + public ObjectiveProgressInformation(Object context) { + this.context = context; + + objectiveProgressStatus = false; + objectiveSatisfiedStatus = false; + + objectiveMeasureStatus = false; + objectiveNormalizedMeasure = new DecimalWithRange(0, -1, 1, 4); + + objectiveCompletionStatus = false; + + objectiveCompletionAmountStatus = false; + objectiveCompletionAmount = new DecimalWithRange(0, 0, 1, 4); + } + + public void reinit() { + objectiveProgressStatus = false; + objectiveSatisfiedStatus = false; + + objectiveMeasureStatus = false; + objectiveNormalizedMeasure.setValue(0); + + objectiveCompletionStatus = false; + + objectiveCompletionAmountStatus = false; + objectiveCompletionAmount.setValue(0); + } + + public Object getContext() { + return context; + } + + public boolean isObjectiveProgressStatus() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadObjectiveSatisfiedStatus()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveProgressStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveProgressStatus(); + } else { + return objectiveProgressStatus; + } + } + } + } + return objectiveProgressStatus; + } + + public void setObjectiveProgressStatus(boolean objectiveProgressStatus) { + this.objectiveProgressStatus = objectiveProgressStatus; + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteObjectiveSatisfiedStatus()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveProgressStatus(objectiveProgressStatus); + } + } + } + } + + public boolean isObjectiveSatisfiedStatus() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadObjectiveSatisfiedStatus()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveProgressStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveSatisfiedStatus(); + } else { + return objectiveSatisfiedStatus; + } + } + } + } + return objectiveSatisfiedStatus; + } + + public void setObjectiveSatisfiedStatus(boolean objectiveSatisfiedStatus) { + this.objectiveSatisfiedStatus = objectiveSatisfiedStatus; + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteObjectiveSatisfiedStatus()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveSatisfiedStatus(objectiveSatisfiedStatus); + } + } + } + } + + public boolean isObjectiveMeasureStatus() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadObjectiveNormalizedMeasure()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveMeasureStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveMeasureStatus(); + } else { + return objectiveMeasureStatus; + } + } + } + } + return objectiveMeasureStatus; + } + + public void setObjectiveMeasureStatus(boolean objectiveMeasureStatus) { + this.objectiveMeasureStatus = objectiveMeasureStatus; + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteObjectiveNormalizedMeasure()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveMeasureStatus(objectiveMeasureStatus); + } + } + } + } + + public DecimalWithRange getObjectiveNormalizedMeasure() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadObjectiveNormalizedMeasure()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveMeasureStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().getObjectiveNormalizedMeasure(); + } else { + return objectiveNormalizedMeasure; + } + } + } + } + return objectiveNormalizedMeasure; + } + + public void setObjectiveNormalizedMeasure(double objectiveNormalizedMeasure) { + this.objectiveNormalizedMeasure.setValue(objectiveNormalizedMeasure); + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteObjectiveNormalizedMeasure()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveNormalizedMeasure(objectiveNormalizedMeasure); + } + } + } + } + + public boolean isObjectiveCompletionProgressStatus() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadCompletionStatus()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveCompletionProgressStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveCompletionProgressStatus(); + } else { + return objectiveCompletionProgressStatus; + } + } + } + } + return objectiveCompletionProgressStatus; + } + + public void setObjectiveCompletionProgressStatus(boolean objectiveCompletionProgressStatus) { + this.objectiveCompletionProgressStatus = objectiveCompletionProgressStatus; + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteCompletionStatus()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveCompletionProgressStatus(objectiveCompletionProgressStatus); + } + } + } + } + + public boolean isObjectiveCompletionStatus() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadCompletionStatus()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveCompletionProgressStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveCompletionStatus(); + } else { + return objectiveSatisfiedStatus; + } + } + } + } + return objectiveCompletionStatus; + } + + public void setObjectiveCompletionStatus(boolean objectiveCompletionStatus) { + this.objectiveCompletionStatus = objectiveCompletionStatus; + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteCompletionStatus()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveCompletionStatus(objectiveCompletionStatus); + } + } + } + } + + public boolean isObjectiveCompletionAmountStatus() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadProgressMeasure()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveCompletionAmountStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveCompletionAmountStatus(); + } else { + return objectiveCompletionAmountStatus; + } + } + } + } + return objectiveCompletionAmountStatus; + } + + public void setObjectiveCompletionAmountStatus(boolean objectiveCompletionAmountStatus) { + this.objectiveCompletionAmountStatus = objectiveCompletionAmountStatus; + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteProgressMeasure()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveCompletionAmountStatus(objectiveCompletionAmountStatus); + } + } + } + } + + public DecimalWithRange getObjectiveCompletionAmount() { + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isReadProgressMeasure()) { + if (globalObjectiveDescription.getObjectiveProgressInformation().isObjectiveCompletionAmountStatus()) { + return globalObjectiveDescription.getObjectiveProgressInformation().getObjectiveCompletionAmount(); + } else { + return objectiveCompletionAmount; + } + } + } + } + return objectiveCompletionAmount; + } + + public void setObjectiveCompletionAmount(double objectiveCompletionAmount) { + this.objectiveCompletionAmount.setValue(objectiveCompletionAmount); + if (context instanceof ObjectiveDescription) { + ObjectiveDescription objectiveDescription = (ObjectiveDescription) context; + for (ObjectiveMap objectiveMap : objectiveDescription.getObjectiveMaps()) { + GlobalObjectiveDescription globalObjectiveDescription = objectiveDescription.getContext().getContext() + .findGlobalObjectiveDescription(objectiveMap.getTargetObjectiveID()); + if (objectiveMap.isWriteProgressMeasure()) { + globalObjectiveDescription.getObjectiveProgressInformation().setObjectiveCompletionAmount(objectiveCompletionAmount); + } + } + } + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tree/Activity.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tree/Activity.java new file mode 100644 index 00000000..174ba1ec --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tree/Activity.java @@ -0,0 +1,219 @@ +package com.xboe.module.scorm.sn.model.tree; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.sn.model.definition.ObjectiveDescription; +import com.xboe.module.scorm.sn.model.definition.HideLmsUIControls; +import com.xboe.module.scorm.sn.model.definition.SequencingDefinition; +import com.xboe.module.scorm.sn.model.tracking.ActivityProgressInformation; +import com.xboe.module.scorm.sn.model.tracking.ActivityStateInformation; +import com.xboe.module.scorm.sn.model.tracking.AttemptProgressInformation; + +public class Activity { + + private final ID id; + + private final SequencingDefinition sequencingDefinition; + + private final ActivityProgressInformation activityProgressInformation; + + private final AttemptProgressInformation attemptProgressInformation; + + private final ActivityStateInformation activityStateInformation; + + private final HideLmsUIControls hideLmsUIControls; + + private boolean visible; + + private String title; + + private String referenceResource; + + private String parameters; + + private Activity parentActivity; + + private final List<Activity> children; + + private final ActivityTree context; + + public Activity(ID id, ActivityTree context) { + this.id = id; + children = new ArrayList<>(); + sequencingDefinition = new SequencingDefinition(this); + activityProgressInformation = new ActivityProgressInformation(this); + attemptProgressInformation = new AttemptProgressInformation(this); + activityStateInformation = new ActivityStateInformation(); + hideLmsUIControls = new HideLmsUIControls(); + visible = true; + title = ""; + referenceResource = ""; + parameters = ""; + this.context = context; + } + + public SequencingDefinition getSequencingDefinition() { + return sequencingDefinition; + } + + public ActivityProgressInformation getActivityProgressInformation() { + return activityProgressInformation; + } + + public AttemptProgressInformation getAttemptProgressInformation() { + return attemptProgressInformation; + } + + public ActivityStateInformation getActivityStateInformation() { + return activityStateInformation; + } + + public HideLmsUIControls getHideLmsUIControls() { + return hideLmsUIControls; + } + + public boolean isVisible() { + return visible; + } + + public Activity setVisible(boolean visible) { + this.visible = visible; + return this; + } + + public List<Activity> getChildren() { + return children; + } + + public Activity getParentActivity() { + return parentActivity; + } + + public void setParentActivity(Activity parentActivity) { + this.parentActivity = parentActivity; + } + + public ID getId() { + return id; + } + + public String getTitle() { + return title; + } + + public Activity setTitle(String title) { + this.title = title; + return this; + } + + public String getReferenceResource() { + return referenceResource; + } + + public Activity setReferenceResource(String referenceResource) { + this.referenceResource = referenceResource; + return this; + } + + public String getParameters() { + return parameters; + } + + public Activity setParameters(String parameters) { + this.parameters = parameters; + return this; + } + + public void addChildren(Activity activity) { + if (activity != null) { + this.children.add(activity); + } + } + + public boolean isSiblingActivity(Activity targetActivity) { + if (parentActivity == null || targetActivity == null || targetActivity.getParentActivity() == null) { + return false; + } + return parentActivity.equals(targetActivity.getParentActivity()); + } + + public ObjectiveDescription findAssociatedObjectiveByID(String objectiveID) { + for (ObjectiveDescription objectiveDescription : sequencingDefinition.getObjectiveDescriptions()) { + if (StringUtils.equals(objectiveID, objectiveDescription.getObjectiveID())) { + return objectiveDescription; + } + } + return null; + } + + public ObjectiveDescription findAssociatedObjectiveForContributingToRollup() { + for (ObjectiveDescription objectiveDescription : sequencingDefinition.getObjectiveDescriptions()) { + if (objectiveDescription.isObjectiveContributesToRollup()) { + return objectiveDescription; + } + } + return null; + } + + public boolean isDescendent(Activity activity) { + if (activity == null || isLeaf()) { + return false; + } + if (activity.equals(this)) { + return false; + } + Activity parent = activity.getParentActivity(); + while (parent != null) { + if (parent.equals(this)) { + return true; + } + parent = parent.getParentActivity(); + } + return false; + } + + public boolean isLeaf() { + return this.children.isEmpty(); + } + + public ID getBelongActivityTreeID() { + return getBelongActivityTreeID(this); + } + + private ID getBelongActivityTreeID(Activity activity) { + if (activity.parentActivity == null) { + return activity.id; + } + return getBelongActivityTreeID(activity.getParentActivity()); + } + + public ActivityTree getContext() { + return context; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Activity activity = (Activity) o; + + return new EqualsBuilder() + .append(id, activity.id) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(id) + .toHashCode(); + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tree/ActivityTree.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tree/ActivityTree.java new file mode 100644 index 00000000..770be26f --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/sn/model/tree/ActivityTree.java @@ -0,0 +1,186 @@ +package com.xboe.module.scorm.sn.model.tree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Stack; + +import com.sun.istack.internal.NotNull; + +import com.xboe.module.scorm.common.ID; +import com.xboe.module.scorm.sn.model.global.GlobalObjectiveDescription; +import com.xboe.module.scorm.sn.model.tracking.GlobalStateInformation; + +public class ActivityTree { + + private final ID id; + private boolean objectivesGlobalToSystem; // SN-3-39 3.10.2 + private Activity root; + private final GlobalStateInformation globalStateInformation; + private final Map<String, GlobalObjectiveDescription> globalObjectiveDescriptionMap; + + public ActivityTree(ID id) { + this.id = id; + objectivesGlobalToSystem = true; + globalStateInformation = new GlobalStateInformation(); + globalObjectiveDescriptionMap = new HashMap<>(); + } + + public boolean isObjectivesGlobalToSystem() { + return objectivesGlobalToSystem; + } + + public void setObjectivesGlobalToSystem(boolean objectivesGlobalToSystem) { + this.objectivesGlobalToSystem = objectivesGlobalToSystem; + } + + public Activity getRoot() { + return root; + } + + public void setRoot(Activity root) { + this.root = root; + } + + public GlobalStateInformation getGlobalStateInformation() { + return globalStateInformation; + } + + public ID getId() { + return id; + } + + public GlobalObjectiveDescription findGlobalObjectiveDescription(String objectiveId) { + GlobalObjectiveDescription globalObjectiveDescription = globalObjectiveDescriptionMap.get(objectiveId); + if (globalObjectiveDescription == null) { + globalObjectiveDescription = new GlobalObjectiveDescription(objectiveId); + globalObjectiveDescriptionMap.put(objectiveId, globalObjectiveDescription); + } + return globalObjectiveDescription; + } + + public boolean isRoot(Activity activity) { + return activity.getParentActivity() == null && Objects.equals(activity, root); + } + + public Activity findActivityByID(ID id) { + for (Activity activity : preorder()) { + if (activity.getId().equals(id)) { + return activity; + } + } + return null; + } + + public boolean existActivity(@NotNull Activity activity) { + if (root == null) { + return false; + } + return existActivity(root, activity); + } + + public boolean existActivity(@NotNull Activity activity, @NotNull Activity target) { + if (activity.equals(target)) { + return true; + } + for (Activity child : activity.getChildren()) { + if (existActivity(child, target)) { + return true; + } + } + return false; + } + + public boolean isAvailable(Activity activity) { + if (isRoot(activity)) { + return true; + } + return activity.getParentActivity().getActivityStateInformation().getAvailableChildren().contains(activity); + } + + public List<Activity> preorder() { + List<Activity> list = new ArrayList<>(); + preorder(list, root); + return Collections.unmodifiableList(list); + } + + private void preorder(List<Activity> resultList, Activity node) { + if (node != null) { + resultList.add(node); + for (int i = 0; i < node.getChildren().size(); i++) { + preorder(resultList, node.getChildren().get(i)); + } + } + } + + public Activity findCommonAncestorFor(Activity oneActivity, Activity twoActivity) { + if (!existActivity(oneActivity) || !existActivity(twoActivity)) { + return null; + } + if (isRoot(oneActivity) || isRoot(twoActivity)) { + return root; + } + if (oneActivity.isSiblingActivity(twoActivity)) { + return oneActivity.getParentActivity(); + } + Stack<Activity> oneStack = new Stack<>(); + Stack<Activity> twoStack = new Stack<>(); + oneStack.push(oneActivity.getParentActivity()); + twoStack.push(twoActivity.getParentActivity()); + while (oneStack.peek().getParentActivity() != null) { + oneStack.push(oneStack.peek().getParentActivity()); + } + while (twoStack.peek().getParentActivity() != null) { + twoStack.push(twoStack.peek().getParentActivity()); + } + Activity commonAncestor = null; + while (!oneStack.isEmpty() && !twoStack.isEmpty() && oneStack.peek().equals(twoStack.peek())) { + commonAncestor = oneStack.pop(); + twoStack.pop(); + } + return commonAncestor; + } + + public static List<Activity> getActivitySequenceList(Activity from, boolean includeFrom, Activity to, boolean includeTo) { + if (from == null || to == null || !from.isSiblingActivity(to) + || from.getParentActivity() == null || to.getParentActivity() == null) { + return Collections.emptyList(); + } + Activity parent = from.getParentActivity(); + int indexOfFrom = parent.getChildren().indexOf(from); + int indexOfTo = parent.getChildren().indexOf(to); + List<Activity> list = new LinkedList<>(); + if (indexOfFrom < indexOfTo) { + if (!includeFrom) { + indexOfFrom++; + } + if (!includeTo) { + indexOfTo--; + } + if (indexOfFrom > indexOfTo) { + return Collections.emptyList(); + } + for (int i = indexOfFrom; i <= indexOfTo; i++) { + list.add(parent.getChildren().get(i)); + } + } else { + if (!includeFrom) { + indexOfFrom--; + } + if (!includeTo) { + indexOfTo++; + } + if (indexOfTo > indexOfFrom) { + return Collections.emptyList(); + } + for (int i = indexOfFrom; i >= indexOfTo; i--) { + list.add(parent.getChildren().get(i)); + } + } + return list; + } +} diff --git a/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/web/ScormPlayer.java b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/web/ScormPlayer.java new file mode 100644 index 00000000..50e08f31 --- /dev/null +++ b/modules/boe-module-scorm/src/main/java/com/xboe/module/scorm/web/ScormPlayer.java @@ -0,0 +1,8 @@ +package com.xboe.module.scorm.web; + +/** + * 全文检索查询实现 + */ +public class ScormPlayer { + +} diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/course/api/CourseManageApi.java b/servers/boe-server-all/src/main/java/com/xboe/module/course/api/CourseManageApi.java index 024d82da..4ea652bb 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/course/api/CourseManageApi.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/course/api/CourseManageApi.java @@ -394,9 +394,9 @@ public class CourseManageApi extends ApiBaseController{ } //此判断用于本地测试 if(!isLocalDevelopment()) { - if(StringUtils.isBlank(dto.getAuditUser().getKid())) { - return badRequest("HRBP审核信息人员错误"); - } +// if(StringUtils.isBlank(dto.getAuditUser().getKid())) { +// return badRequest("HRBP审核信息人员错误"); +// } if(StringUtils.isBlank(dto.getAuditUser().getCode())) { return badRequest("HRBP审核信息人员错误"); @@ -444,16 +444,24 @@ public class CourseManageApi extends ApiBaseController{ return badRequest("此课程中已有审核,不能再提交审核,如修改请先撤回"); } if(!isLocalDevelopment()) { - //转化用户id - User u = userService.getByUserNo(dto.getAuditUser().getCode()); - if(u==null) { //本地没有,需要同步 - //Organization org = orgService.getBySysId(dto.getAuditUser().getOrgkid()); - UserVo fwUser = fwUserService.getById(dto.getAuditUser().getKid()); - Account a =userService.syncAccountUser(fwUser); - dto.getAuditUser().setAid(a.getId()); + if(StringUtils.isBlank(dto.getAuditUser().getAid())) { + if(StringUtils.isBlank(dto.getAuditUser().getKid())) { + return badRequest("HRBP审核信息人员错误"); + } + //转化用户id + User u = userService.getByUserNo(dto.getAuditUser().getCode()); + if(u==null) { //本地没有,需要同步 + //Organization org = orgService.getBySysId(dto.getAuditUser().getOrgkid()); + UserVo fwUser = fwUserService.getById(dto.getAuditUser().getKid()); + Account a =userService.syncAccountUser(fwUser); + dto.getAuditUser().setAid(a.getId()); + }else { + dto.getAuditUser().setAid(u.getId()); + } }else { - dto.getAuditUser().setAid(u.getId()); + log.info("已获取过hrbp审核人id【"+dto.getAuditUser().getAid()+"】,不需要再转化"); } + }else { //弄成固定值,用于测试 dto.getAuditUser().setAid(getCurrent().getAccountId()); @@ -531,9 +539,9 @@ public class CourseManageApi extends ApiBaseController{ return badRequest("未关联HRBP审核人员"); } - if(StringUtils.isBlank(ukid)){ - return badRequest("未关联HRBP审核人员"); - } +// if(StringUtils.isBlank(ukid)){ +// return badRequest("未关联HRBP审核人员"); +// } Course course = courseService.get(audit.getCourseId()); if(StringUtils.isBlank(course.getOrgId())) { @@ -551,19 +559,24 @@ public class CourseManageApi extends ApiBaseController{ } if(!isLocalDevelopment()) { - //转化用户id - User u = userService.getByUserNo(ucode); - if(u==null) { //本地没有,需要同步 - //Organization org = orgService.getBySysId(dto.getAuditUser().getOrgkid()); - UserVo fwUser = fwUserService.getById(ukid); - Account a =userService.syncAccountUser(fwUser); - audit.setAid(a.getId()); - }else { - audit.setAid(u.getId()); - } if(StringUtils.isBlank(audit.getAid())) { - return badRequest("查询组织HRBP审核人员失败"); + if(StringUtils.isBlank(ukid)){ + return badRequest("未关联HRBP审核人员"); + } + //转化用户id + User u = userService.getByUserNo(ucode); + if(u==null) { //本地没有,需要同步 + //Organization org = orgService.getBySysId(dto.getAuditUser().getOrgkid()); + UserVo fwUser = fwUserService.getById(ukid); + Account a =userService.syncAccountUser(fwUser); + audit.setAid(a.getId()); + }else { + audit.setAid(u.getId()); + } + }else { + log.info("已获取过hrbp审核人id【"+audit.getAid()+"】,不需要再转化"); } + }else { //弄成固定值,用于测试 audit.setAid(getCurrent().getAccountId()); diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/course/entity/CourseCrowd.java b/servers/boe-server-all/src/main/java/com/xboe/module/course/entity/CourseCrowd.java index 2222941c..abef4413 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/course/entity/CourseCrowd.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/course/entity/CourseCrowd.java @@ -32,7 +32,7 @@ public class CourseCrowd extends IdBaseEntity{ /** * 群组,受众id */ - @Column(name = "group_id",nullable=false,length=20) + @Column(name = "group_id",nullable=false,length=36) private String groupId; /** diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/course/service/impl/CourseServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/course/service/impl/CourseServiceImpl.java index 69780a79..6bfbb8b8 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/course/service/impl/CourseServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/course/service/impl/CourseServiceImpl.java @@ -417,6 +417,7 @@ public class CourseServiceImpl implements ICourseService { query.setPageSize(num); query.addFields("new Course(id,type,name,coverImg,score,studys,comments,shares,praises,favorites,forUsers,value,summary,publishTime,isTop,sysType1,sysType2,sysType3)"); List<Course> courseList = courseDao.findList(query.builder()); + courseList.addAll(courses); return courseList; } diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/exam/api/AloneExamExtendApi.java b/servers/boe-server-all/src/main/java/com/xboe/module/exam/api/AloneExamExtendApi.java index 0bfc581d..cf01f5e6 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/exam/api/AloneExamExtendApi.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/exam/api/AloneExamExtendApi.java @@ -9,12 +9,10 @@ import com.xboe.module.exam.entity.AloneExam; import com.xboe.module.exam.entity.AloneExamAnswer; import com.xboe.module.exam.service.IAloneExamService; import com.xboe.module.exam.vo.AloneExamQuery; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import java.util.List; /** * 对于与第三方对接的扩展接口 @@ -31,16 +29,16 @@ public class AloneExamExtendApi extends ApiBaseController { * */ @PostMapping("/save") public JsonResponse<AloneExam> save(@RequestBody AloneExam aloneExam){ - if(StringUtil.isNotBlank(aloneExam.getAid())){ + if(StringUtil.isBlank(aloneExam.getAid())){ return badRequest("缺少用户参数"); } - if(StringUtil.isNotBlank(aloneExam.getTestId())){ + if(StringUtil.isBlank(aloneExam.getTestId())){ return badRequest("缺少考试信息"); } - if(StringUtil.isNotBlank(aloneExam.getRefId())){ + if(StringUtil.isBlank(aloneExam.getRefId())){ return badRequest("缺少关联信息"); } - if(StringUtil.isNotBlank(aloneExam.getRefType())){ + if(StringUtil.isBlank(aloneExam.getRefType())){ return badRequest("缺少关联类型"); } try { @@ -68,7 +66,7 @@ public class AloneExamExtendApi extends ApiBaseController { /** * 根据考试id,refType,refId 用户id 查询用户答卷信息 * */ - @PostMapping("/anser-page") + @PostMapping("/answer-page") public JsonResponse<PageList<AloneExamAnswer>> answerpage(Pagination pager,AloneExamQuery aloneExamQuery){ try { PageList<AloneExamAnswer> pageList = aloneExamService.answerPage(pager.getPageIndex(), pager.getPageSize(), aloneExamQuery); @@ -78,6 +76,18 @@ public class AloneExamExtendApi extends ApiBaseController { } } + /** + * 根据考试任务id查询答卷 + * */ + @GetMapping("/answer") + public JsonResponse<List<AloneExamAnswer>> answer(String aloneId){ + if(StringUtil.isBlank(aloneId)){ + return badRequest("参数异常"); + } + List<AloneExamAnswer> answer = aloneExamService.findAnswer(aloneId); + return success(answer); + } + } diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/exam/entity/AloneExamAnswer.java b/servers/boe-server-all/src/main/java/com/xboe/module/exam/entity/AloneExamAnswer.java index 530e3b44..e4377131 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/exam/entity/AloneExamAnswer.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/exam/entity/AloneExamAnswer.java @@ -201,4 +201,28 @@ public class AloneExamAnswer extends IdBaseEntity { this.useSecond=useSecond; } + public AloneExamAnswer(String id, String aid, String name, String testId, String testName, Integer arrange, + Integer passLine, Integer status, LocalDateTime startTime, LocalDateTime endTime, + LocalDateTime lastTime, String clientId,String ucode,Float score, + Float totalScore,Float realScore,Integer useSecond,String aloneId) { + this.setId(id); + this.aid = aid; + this.name = name; + this.testId = testId; + this.testName = testName; + this.arrange = arrange; + this.passLine = passLine; + this.status = status; + this.startTime = startTime; + this.endTime = endTime; + this.lastTime = lastTime; + this.clientIp = clientId; + this.ucode=ucode; + this.score=score; + this.totalScore=totalScore; + this.realScore=realScore; + this.useSecond=useSecond; + this.aloneId=aloneId; + } + } diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/IAloneExamService.java b/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/IAloneExamService.java index aa42c2ea..bdfcf39d 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/IAloneExamService.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/IAloneExamService.java @@ -132,5 +132,10 @@ public interface IAloneExamService { * 保存考试任务 * */ void save(AloneExam aloneExam); + + /** + * 根据考试任务id 返回答卷信息 + * */ + List<AloneExamAnswer> findAnswer(String aloneId); } diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/impl/AloneExamServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/impl/AloneExamServiceImpl.java index 347c8263..7482f336 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/impl/AloneExamServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/exam/service/impl/AloneExamServiceImpl.java @@ -156,10 +156,21 @@ public class AloneExamServiceImpl implements IAloneExamService{ } @Override + @Transactional public void save(AloneExam aloneExam) { aeDao.save(aloneExam); } + @Override + public List<AloneExamAnswer> findAnswer(String aloneId) { + QueryBuilder builder = QueryBuilder.from(AloneExamAnswer.class); + builder.addFilter(FieldFilters.eq("aloneId",aloneId)); + builder.addOrder(OrderCondition.desc("sysCreateTime")); + builder.addField("new AloneExamAnswer(id,aid,name,testId,testName,arrange,passLine,status,startTime,endTime,lastTime,clientIp,ucode,score,totalScore,realScore,useSecond,aloneId)"); + List<AloneExamAnswer> list = dao.findList(builder.builder()); + return list; + } + @Override public AloneExamAnswer get(String id) { return dao.get(id); @@ -248,6 +259,9 @@ public class AloneExamServiceImpl implements IAloneExamService{ if(StringUtil.isNotBlank(examQuery.getRefType())){ builder.addFilter(FieldFilters.eq("a.refType",examQuery.getRefType())); } + if(StringUtil.isNotBlank(examQuery.getAid())){ + builder.addFilter(FieldFilters.eq("a.aid",examQuery.getAid())); + } } PageList<AloneExam> page = aeDao.findPage(builder.builder()); @@ -257,7 +271,7 @@ public class AloneExamServiceImpl implements IAloneExamService{ @Override public PageList<AloneExamAnswer> answerPage(int pageIndex, int pageSize, AloneExamQuery aloneExamQuery) { QueryBuilder builder=QueryBuilder.from(AloneExam.class.getSimpleName()+" a,"+AloneExamAnswer.class.getSimpleName()+" aa"); - builder.addField("new AloneExamAnswer(aa.id,aa.aid,aa.name,aa.testId,aa.testName,aa.arrange,aa.passLine,aa.status,aa.startTime,aa.endTime,aa.lastTime,aa.clientIp,aa.ucode,aa.score,aa.totalScore,aa.realScore,aa.useSecond)"); + builder.addField("new AloneExamAnswer(aa.id,aa.aid,aa.name,aa.testId,aa.testName,aa.arrange,aa.passLine,aa.status,aa.startTime,aa.endTime,aa.lastTime,aa.clientIp,aa.ucode,aa.score,aa.totalScore,aa.realScore,aa.useSecond,aa.aloneId)"); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); builder.addFilter(FieldFilters.eqField("a.id","aa.aloneId")); diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/api/CommentsApi.java b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/api/CommentsApi.java index 1573092c..0d4f124a 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/api/CommentsApi.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/api/CommentsApi.java @@ -169,6 +169,47 @@ public class CommentsApi extends ApiBaseController{ return error("查询@我的数据失败",e.getMessage()); } } + + /** + * 查询@我的 + * @param pager + * @param type 类型,课程 ,文章,问题 + * @param send + * @param isread 是否已读,对应已回答的 + * @return + */ + @RequestMapping(value="/tome/mobilepage",method= {RequestMethod.GET,RequestMethod.POST}) + public JsonResponse<PageList<CommentsDto>> tomeMobilePage(Pagination pager,String send,Boolean isread){ + String aid=getCurrent().getAccountId(); + PageList<CommentsDto> all = service.findMobileAll(pager.getPageIndex(), pager.getPageSize(), aid, send); + return success(all); +// try { +// if(type==1) { +// PageList<CommentsDto> list=service.findCourseTome(pager.getPageIndex(),pager.getPageSize(), type, aid, send,isread); +// //查询二级回复内容 +// return success(list); +// } +// if(type==2){ +// PageList<CommentsDto> list=service.findArticleTome(pager.getPageIndex(),pager.getPageSize(), type, aid, send,isread); +// //查询二级回复内容 +// return success(list); +// } +// if(type==3){ +// PageList<CommentsDto> casesTome = service.findCasesTome(pager.getPageIndex(), pager.getPageSize(), type, aid, send, isread); +// return success(casesTome); +// } +// if(type==4){ +// PageList<CommentsDto> list = service.findQaTome(pager.getPageIndex(), pager.getPageSize(), type, aid, send, isread); +// return success(list); +// } +// else { +// return success(new PageList<CommentsDto>()); +// } +// }catch(Exception e) { +// return error("查询@我的数据失败",e.getMessage()); +// } + } + @PostMapping("/add") @AutoLog(module = "评论",action = "添加评论",info = "添加评论内容") diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/ICommentsService.java b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/ICommentsService.java index aab4a830..81718cad 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/ICommentsService.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/ICommentsService.java @@ -136,6 +136,12 @@ public interface ICommentsService { * */ PageList<CommentsDto> findAll(int pageIndex, int pageSize, String toaid, String uname); + /** + * 查询@我的全部 去掉案例和笔记 + * 问答和其他三个结构不一样 + * */ + PageList<CommentsDto> findMobileAll(int pageIndex, int pageSize, String toaid, String uname); + /** * diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/CommentsServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/CommentsServiceImpl.java index dbd955f4..6d185658 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/CommentsServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/CommentsServiceImpl.java @@ -296,6 +296,88 @@ public class CommentsServiceImpl implements ICommentsService{ } + @Override + public PageList<CommentsDto> findMobileAll(int pageIndex, int pageSize, String toaid, String uname) { + PageList<CommentsDto> list = new PageList<>(); + list.setList(new ArrayList<CommentsDto>()); + QueryBuilder builder = QueryBuilder.from(CommentQa.class); +// builder.addFilter(FieldFilters.eq("deleted",false)); +// builder.addFilter(FieldFilters.ne("objType",4)); + if(StringUtil.isNotBlank(toaid)){ + builder.addFilter(FieldFilters.eq("toAid",toaid)); + } + + if(StringUtil.isNotBlank(uname)){ + builder.addFilter(FieldFilters.eq("sysCreateBy",uname)); + } + builder.addOrder(OrderCondition.desc("sysCreateTime")); + builder.setPageIndex(pageIndex); + builder.setPageSize(pageSize); + //查出所有@我的数据 + PageList<CommentQa> page = commentQaDao.findPage(builder.builder()); + //问答 + List<String> qaStrings = new ArrayList<>(); + //文章 + List<String> articleStrings = new ArrayList<>(); + for (CommentQa c:page.getList()) { + if(c.getRefType()!=3) { + CommentsDto commentsDto = new CommentsDto(); + commentsDto.setCommentId(c.getId()); + commentsDto.setObjId(c.getRefId()); + commentsDto.setObjType(c.getRefType()); + commentsDto.setToAid(c.getToAid()); + commentsDto.setToAname(c.getToName()); + commentsDto.setContent(c.getContent()); + commentsDto.setSysCreateAid(c.getSysCreateAid()); + commentsDto.setSysCreateBy(c.getSysCreateBy()); + commentsDto.setSysCreateTime(c.getSysCreateTime()); + list.getList().add(commentsDto); + + if (c.getRefType() == 4) { + qaStrings.add(c.getRefId()); + } + if (c.getRefType() == 2) { + articleStrings.add(c.getRefId()); + } + } + } + if(qaStrings!=null && !qaStrings.isEmpty()){ + QueryBuilder builder1 = QueryBuilder.from(Question.class); + builder1.addFilter(FieldFilters.in("id",qaStrings)); + builder1.addFields("new Question(id,title)"); + List<Question> qaList = questionDao.findList(builder1.builder()); + + if(qaList!=null && !qaList.isEmpty()){ + for (Question q:qaList) { + for (CommentsDto c:list.getList()) { + if(q.getId().equals(c.getObjId())){ + c.setTitle(q.getTitle()); + } + } + } + } + } + + if(articleStrings!=null && !articleStrings.isEmpty()){ + QueryBuilder builder1 = QueryBuilder.from(Article.class); + builder1.addFilter(FieldFilters.in("id",articleStrings)); + builder1.addFields("new Article(id,title)"); + List<Article> articleList = articleDao.findList(builder1.builder()); + if(articleList!=null && !articleList.isEmpty()){ + for (Article a:articleList) { + for (CommentsDto c:list.getList()) { + if(a.getId().equals(c.getObjId())){ + c.setTitle(a.getTitle()); + } + } + } + } + } + list.setCount(page.getCount()); + list.setPageSize(pageSize); + return list; + } + @Override public List<Comments> list(Integer objType, String objId) { List<Comments> list = dao.findList(FieldFilters.eq("objType", objType), FieldFilters.eq("objId", objId)); diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/FavoritesServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/FavoritesServiceImpl.java index c329af1e..9cced033 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/FavoritesServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/FavoritesServiceImpl.java @@ -129,7 +129,7 @@ public class FavoritesServiceImpl implements IFavoritesService{ builder.addFilter(FieldFilters.eq("sysCreateAid",aid)); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("id")); + builder.addOrder(OrderCondition.desc("sysCreateTime")); if(StringUtils.isNotBlank(keyWord)){ builder.addFilter(FieldFilters.like("title", LikeMatchMode.ANYWHERE,keyWord)); } @@ -407,7 +407,7 @@ public class FavoritesServiceImpl implements IFavoritesService{ QueryBuilder query=QueryBuilder.from(Favorites.class); query.setPageIndex(pageIndex); query.setPageSize(pageSize); - query.addOrder(OrderCondition.desc("id")); + query.addOrder(OrderCondition.desc("sysCreateTime")); if(favorite.getObjType()!=null) { query.addFilter(FieldFilters.eq("objType", favorite.getObjType())); } @@ -442,7 +442,7 @@ public class FavoritesServiceImpl implements IFavoritesService{ query.addFields("c.deadTime","c.views","c.comments","c.praises","c.shares","c.favorites","f.objType","c.score"); query.setPageIndex(pageIndex); query.setPageSize(pageSize); - query.addOrder(OrderCondition.desc("f.id")); + query.addOrder(OrderCondition.desc("f.sysCreateTime")); query.addFilter(FieldFilters.eqField("f.objId","c.id")); query.addFilter(FieldFilters.eq("f.objType",BoedxResourceType.Course.value())); //查询用户自己的课程收藏, @@ -511,7 +511,7 @@ public class FavoritesServiceImpl implements IFavoritesService{ QueryBuilder query=QueryBuilder.from(from); query.setPageIndex(pageIndex); query.setPageSize(pageSize); - query.addOrder(OrderCondition.desc("f.id")); + query.addOrder(OrderCondition.desc("f.sysCreateTime")); query.addFilter(FieldFilters.eqField("f.objId","q.id")); query.addFilter(FieldFilters.eq("f.objType",BoedxResourceType.QA.value())); //查询用户自己的问答收藏, @@ -589,7 +589,7 @@ public class FavoritesServiceImpl implements IFavoritesService{ QueryBuilder builder=QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("f.id")); + builder.addOrder(OrderCondition.desc("f.sysCreateTime")); builder.addFilter(FieldFilters.eqField("f.objId","a.id")); builder.addFilter(FieldFilters.eq("f.objType",BoedxResourceType.Answer.value())); builder.addFilter(FieldFilters.eq("f.sysCreateAid",aid)); @@ -640,7 +640,7 @@ public class FavoritesServiceImpl implements IFavoritesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("f.id")); + builder.addOrder(OrderCondition.desc("f.sysCreateTime")); builder.addFilter(FieldFilters.eqField("f.objId","c.id")); builder.addFilter(FieldFilters.eq("f.objType",BoedxResourceType.Case.value())); builder.addFilter(FieldFilters.eq("f.sysCreateAid",aid)); @@ -685,7 +685,7 @@ public class FavoritesServiceImpl implements IFavoritesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("f.id")); + builder.addOrder(OrderCondition.desc("f.sysCreateTime")); builder.addFilter(FieldFilters.eqField("f.objId","a.id")); builder.addFilter(FieldFilters.eq("f.objType",BoedxResourceType.Article.value())); builder.addFilter(FieldFilters.eq("f.sysCreateAid",aid)); diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/SharesServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/SharesServiceImpl.java index e1e1b3e2..fd083a0f 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/SharesServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/interaction/service/impl/SharesServiceImpl.java @@ -114,7 +114,7 @@ public class SharesServiceImpl implements ISharesService{ QueryBuilder builder = QueryBuilder.from(Shares.class); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("id")); + builder.addOrder(OrderCondition.desc("sysCreateTime")); if(shares.getObjType()!=null){ builder.addFilter(FieldFilters.eq("objType",shares.getObjType())); } @@ -163,7 +163,7 @@ public class SharesServiceImpl implements ISharesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","q.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.QA.value())); builder.addFilter(FieldFilters.eq("s.sysCreateAid",aid)); @@ -211,7 +211,7 @@ public class SharesServiceImpl implements ISharesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","c.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.Case.value())); builder.addFilter(FieldFilters.eq("s.sysCreateAid",aid)); @@ -264,7 +264,7 @@ public class SharesServiceImpl implements ISharesService{ builder.addFields("c.deadTime","c.views","c.comments","c.praises","c.shares","c.favorites,s.isRead"); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","c.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.Course.value())); builder.addFilter(FieldFilters.eq("s.sysCreateAid",aid)); @@ -324,7 +324,7 @@ public class SharesServiceImpl implements ISharesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","a.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.Article.value())); builder.addFilter(FieldFilters.eq("s.sysCreateAid",aid)); @@ -382,7 +382,7 @@ public class SharesServiceImpl implements ISharesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","q.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.QA.value())); builder.addFilter(FieldFilters.eq("s.toAid",aid)); @@ -434,7 +434,7 @@ public class SharesServiceImpl implements ISharesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","c.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.Case.value())); builder.addFilter(FieldFilters.eq("s.toAid",aid)); @@ -487,7 +487,7 @@ public class SharesServiceImpl implements ISharesService{ builder.addFields("c.deadTime","c.views","c.comments","c.praises","c.shares","c.favorites","s.isRead"); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","c.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.Course.value())); builder.addFilter(FieldFilters.eq("s.toAid",aid)); @@ -547,7 +547,7 @@ public class SharesServiceImpl implements ISharesService{ QueryBuilder builder = QueryBuilder.from(from); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("s.id")); + builder.addOrder(OrderCondition.desc("s.sysCreateTime")); builder.addFilter(FieldFilters.eqField("s.objId","a.id")); builder.addFilter(FieldFilters.eq("s.objType",BoedxResourceType.Article.value())); builder.addFilter(FieldFilters.eq("s.toAid",aid)); @@ -623,7 +623,7 @@ public class SharesServiceImpl implements ISharesService{ builder.addFilter(FieldFilters.eq("sysCreateAid",aid)); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("id")); + builder.addOrder(OrderCondition.desc("sysCreateTime")); //关键字查询 if(StringUtils.isNotBlank(keyword)){ builder.addFilter(FieldFilters.or(FieldFilters.like("content",LikeMatchMode.ANYWHERE,keyword), @@ -787,7 +787,7 @@ public class SharesServiceImpl implements ISharesService{ builder.addFilter(FieldFilters.eq("toAid",aid)); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); - builder.addOrder(OrderCondition.desc("id")); + builder.addOrder(OrderCondition.desc("sysCreateTime")); //关键字查询 if(StringUtils.isNotBlank(keyword)){ builder.addFilter(FieldFilters.or(FieldFilters.like("content",LikeMatchMode.ANYWHERE,keyword), diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/qa/service/impl/QuestionServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/qa/service/impl/QuestionServiceImpl.java index ff1c5d99..d7c61317 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/qa/service/impl/QuestionServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/qa/service/impl/QuestionServiceImpl.java @@ -309,7 +309,7 @@ public class QuestionServiceImpl implements IQuestionService { builder.addFilter(FieldFilters.eq("q.status",1)); builder.addFilter(FieldFilters.eq("a.sysCreateAid",aid)); builder.addFilter(FieldFilters.eqField("a.qid","q.id")); - builder.addOrder(OrderCondition.desc("a.id")); + builder.addOrder(OrderCondition.desc("a.sysCreateTime")); builder.setPageIndex(pageIndex); builder.setPageSize(pageSize); builder.addFields("q.id","q.title","q.content","a.id","a.qid","a.content","q.sysCreateTime","a.sysCreateTime", diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/tools/api/RedisCommandApi.java b/servers/boe-server-all/src/main/java/com/xboe/module/tools/api/RedisCommandApi.java index 79d4d3f5..6bc5c780 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/tools/api/RedisCommandApi.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/tools/api/RedisCommandApi.java @@ -2,8 +2,11 @@ package com.xboe.module.tools.api; import com.xboe.common.utils.StringUtil; +import com.xboe.constants.CacheName; import com.xboe.core.JsonResponse; import com.xboe.core.api.ApiBaseController; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Caching; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,4 +29,23 @@ public class RedisCommandApi extends ApiBaseController { Boolean delete = redisTemplate.delete(key); return success(delete); } + + + @GetMapping("/remove-usercache") + public JsonResponse<Boolean> removeUserCache(String id){ + if(StringUtil.isBlank(id)){ + return badRequest("参数异常"); + } + String accountId="user::account:"+id; + String userId="user::user:"+id; + try { + redisTemplate.delete(accountId); + redisTemplate.delete(userId); + return success(true); + } catch (Exception e) { + return error("清楚失败",e.getMessage()); + } + + + } } diff --git a/servers/boe-server-all/src/main/java/com/xboe/system/user/api/MessageApi.java b/servers/boe-server-all/src/main/java/com/xboe/system/user/api/MessageApi.java index 5c28d2f1..4d7ff5d7 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/system/user/api/MessageApi.java +++ b/servers/boe-server-all/src/main/java/com/xboe/system/user/api/MessageApi.java @@ -45,6 +45,13 @@ public class MessageApi extends ApiBaseController { PageList<Message> list = service.query(pager.getPageIndex(), pager.getPageSize(), aid, isRead); return success(list); } + + @PostMapping("/mobilelist") + public JsonResponse<PageList<Message>> mobileList(Pagination pager,Boolean isRead){ + String aid = this.getCurrent().getAccountId(); + PageList<Message> list = service.queryMobile(pager.getPageIndex(), pager.getPageSize(), aid, isRead); + return success(list); + } /** * 删除消息 * @param ids 所勾选的id集合 @@ -210,6 +217,10 @@ public class MessageApi extends ApiBaseController { return success(messages); } + /** + * + * */ + } diff --git a/servers/boe-server-all/src/main/java/com/xboe/system/user/service/IMessageService.java b/servers/boe-server-all/src/main/java/com/xboe/system/user/service/IMessageService.java index 6411edc1..3b52343b 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/system/user/service/IMessageService.java +++ b/servers/boe-server-all/src/main/java/com/xboe/system/user/service/IMessageService.java @@ -16,6 +16,11 @@ public interface IMessageService { * */ PageList<Message> query(int pageIndex,int pageSize,String aid,Boolean isRead); + /** + * 查看消息 + * */ + PageList<Message> queryMobile(int pageIndex,int pageSize,String aid,Boolean isRead); + /** * 删除消息 * */ diff --git a/servers/boe-server-all/src/main/java/com/xboe/system/user/service/impl/MessageServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/system/user/service/impl/MessageServiceImpl.java index e6944179..5524d133 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/system/user/service/impl/MessageServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/system/user/service/impl/MessageServiceImpl.java @@ -7,6 +7,7 @@ import java.util.Map; import javax.annotation.Resource; import com.xboe.common.OrderCondition; +import com.xboe.core.orm.FilterCheckValue; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,6 +43,23 @@ public class MessageServiceImpl implements IMessageService { return list; } + @Override + public PageList<Message> queryMobile(int pageIndex, int pageSize, String aid, Boolean isRead) { + QueryBuilder builder = QueryBuilder.from(Message.class); + builder.addOrder(OrderCondition.desc("id")); + builder.setPageIndex(pageIndex); + builder.setPageSize(pageSize); + builder.addFilter(FieldFilters.eq("acceptId",aid)); + builder.addFilter(FieldFilters.ne("refType","3")); + builder.addFilter(FieldFilters.ne("refType","6")); +// builder.addFilter(FieldFilters.ne("refType","3", FilterCheckValue.NONE)); + if(isRead!=null){ + builder.addFilter(FieldFilters.eq("isRead",isRead)); + } + PageList<Message> list = dao.findPage(builder.builder()); + return list; + } + @Override public void delete(List<String> ids) { dao.deleteByFilter(FieldFilters.in("id",ids)); diff --git a/servers/boe-server-case/pom.xml b/servers/boe-server-case/pom.xml index 87dda106..e947ee44 100644 --- a/servers/boe-server-case/pom.xml +++ b/servers/boe-server-case/pom.xml @@ -23,11 +23,6 @@ <artifactId>xboe-core</artifactId> <version>1.0.0</version> </dependency> - <dependency> - <groupId>com.xboe</groupId> - <artifactId>xboe-redis</artifactId> - <version>1.0.0</version> - </dependency> <dependency> <groupId>com.xboe</groupId> <artifactId>xboe-module-event</artifactId> @@ -70,20 +65,11 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-redis</artifactId> - </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> - <dependency> - <groupId>org.springframework.session</groupId> - <artifactId>spring-session-data-redis</artifactId> - </dependency> - - <dependency> + <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.27</version> diff --git a/servers/boe-server-case/src/main/java/com/xboe/casetask/CaseDataSyncRunner.java b/servers/boe-server-case/src/main/java/com/xboe/casetask/CaseDataSyncRunner.java index 1f88d5ff..a87aaeba 100644 --- a/servers/boe-server-case/src/main/java/com/xboe/casetask/CaseDataSyncRunner.java +++ b/servers/boe-server-case/src/main/java/com/xboe/casetask/CaseDataSyncRunner.java @@ -27,6 +27,7 @@ import com.xboe.casetask.entity.CaseSyncCustomize; import com.xboe.casetask.entity.CaseSyncLog; import com.xboe.casetask.entity.CaseSyncRecord; import com.xboe.casetask.entity.DictItem; +import com.xboe.casetask.entity.User; import com.xboe.casetask.service.ICaseSyncLogService; import com.xboe.casetask.service.ICaseSyncService; import com.xboe.common.utils.StringUtil; @@ -317,7 +318,7 @@ public class CaseDataSyncRunner { } - //用户的转化处理,各处异常处理 + //用户的转化处理,各种异常处理 String owner =csr.getCaseOwner(); if(StringUtils.isNotBlank(owner)) { int leftIdx=owner.indexOf("<"); @@ -336,8 +337,17 @@ public class CaseDataSyncRunner { } }else { - csr.setStatus(1); - csr.setErrorInfo(csr.getErrorInfo()+",caseOwner数据格式错误【"+owner+"】"); + //这种情况把它当成单独的工号处理 + User u= syncService.getByUserNo(owner); + if(u==null) { + csr.setStatus(1); + csr.setErrorInfo(csr.getErrorInfo()+",关联用户失败【"+csr.getCaseOwner()+"】"); + }else { + csr.setAuthorId(u.getId()); + csr.setAuthorName(u.getName()); + } +// csr.setStatus(1); +// csr.setErrorInfo(csr.getErrorInfo()+",caseOwner数据格式错误【"+owner+"】"); } }else { csr.setStatus(1); diff --git a/servers/boe-server-case/src/main/java/com/xboe/casetask/entity/CasesMajorType.java b/servers/boe-server-case/src/main/java/com/xboe/casetask/entity/CasesMajorType.java index ba777e84..a72dc8b5 100644 --- a/servers/boe-server-case/src/main/java/com/xboe/casetask/entity/CasesMajorType.java +++ b/servers/boe-server-case/src/main/java/com/xboe/casetask/entity/CasesMajorType.java @@ -15,7 +15,7 @@ import java.util.Set; * */ @Data -@Entity +@Entity @EqualsAndHashCode(callSuper = false) @Table(name = SysConstant.TABLE_PRE+"cases_major_type") public class CasesMajorType extends IdEntity{ diff --git a/servers/boe-server-case/src/main/java/com/xboe/casetask/service/ICaseSyncService.java b/servers/boe-server-case/src/main/java/com/xboe/casetask/service/ICaseSyncService.java index 05b43b93..8bf05c5c 100644 --- a/servers/boe-server-case/src/main/java/com/xboe/casetask/service/ICaseSyncService.java +++ b/servers/boe-server-case/src/main/java/com/xboe/casetask/service/ICaseSyncService.java @@ -7,6 +7,7 @@ import com.xboe.casetask.entity.CaseSyncCustomize; import com.xboe.casetask.entity.CaseSyncRecord; import com.xboe.casetask.entity.Cases; import com.xboe.casetask.entity.DictItem; +import com.xboe.casetask.entity.User; import com.xboe.common.PageList; public interface ICaseSyncService { @@ -18,6 +19,13 @@ public interface ICaseSyncService { */ String getIdByUserNo(String userNo); + /** + * 根据工号,查询用户信息 + * @param userNo + * @return + */ + User getByUserNo(String userNo); + /** * 获得所有的组织领域 * @return diff --git a/servers/boe-server-case/src/main/java/com/xboe/casetask/service/impl/CaseSyncServiceImpl.java b/servers/boe-server-case/src/main/java/com/xboe/casetask/service/impl/CaseSyncServiceImpl.java index d415b56b..0d403c93 100644 --- a/servers/boe-server-case/src/main/java/com/xboe/casetask/service/impl/CaseSyncServiceImpl.java +++ b/servers/boe-server-case/src/main/java/com/xboe/casetask/service/impl/CaseSyncServiceImpl.java @@ -21,6 +21,7 @@ import com.xboe.casetask.entity.CaseSyncRecord; import com.xboe.casetask.entity.Cases; import com.xboe.casetask.entity.CasesMajorType; import com.xboe.casetask.entity.DictItem; +import com.xboe.casetask.entity.User; import com.xboe.casetask.service.ICaseSyncService; import com.xboe.common.OrderCondition; import com.xboe.common.PageList; @@ -314,6 +315,10 @@ public class CaseSyncServiceImpl implements ICaseSyncService{ if(record.getStatus()!=null) { query.addFilter(FieldFilters.eq("status", record.getStatus())); } + //特定的需求过滤 + if(StringUtils.isNotBlank(record.getErrorInfo())) { + query.addFilter(FieldFilters.ne("errorInfo", record.getErrorInfo())); + } } return recordDao.findPage(query.builder()); @@ -347,4 +352,10 @@ public class CaseSyncServiceImpl implements ICaseSyncService{ return recordDao.findDictItemByName(name1, name2); } + @Override + public User getByUserNo(String userNo) { + User user=userDao.findOne(FieldFilters.eq("userNo", userNo)); + return user; + } + } diff --git a/servers/boe-server-case/src/main/resources/application-dev.properties b/servers/boe-server-case/src/main/resources/application-dev.properties index 58a6b802..2f5a4bfe 100644 --- a/servers/boe-server-case/src/main/resources/application-dev.properties +++ b/servers/boe-server-case/src/main/resources/application-dev.properties @@ -1,32 +1,18 @@ -## redis -spring.redis.database=2 -spring.redis.host=127.0.0.1 -spring.redis.password=ENC(zA5LNV8xw3yEx6LMwdGGBGgNsOaD3Cg+) -spring.redis.port=6379 - ## datasource config spring.datasource.driverClassName=com.mysql.jdbc.Driver # spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boe_base3?useSSL=false&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boeu_base?useSSL=false&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull spring.datasource.username=root spring.datasource.password=ENC(lAoFOYuc8CAypPtigTNLYg==) logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE -## 静态文件目录,默认是在static下面,以后独立到nginx下面配置 -spring.web.resources.static-locations=file:E:/Projects/BOE/java/static + ## xboe config xboe.api.cross_filter=true -## 上传相磁的路径配置 -xboe.upload.file.temp_path=E:/Projects/BOE/java/static/temp -xboe.upload.file.save_path=E:/Projects/BOE/java/static/upload -xboe.upload.file.http_path=http://localhost:9090/cdn/upload - -## 外部接口调用地址 旧系统机构及用户数据接口 -xboe.externalinterface.url.system=http://localhost:9091 ## 案例接口配置,init 是否需要初始化 xboe.case.sync.init=true diff --git a/servers/boe-server-case/src/main/resources/application-pre.properties b/servers/boe-server-case/src/main/resources/application-pre.properties index 4423487d..76ba2307 100644 --- a/servers/boe-server-case/src/main/resources/application-pre.properties +++ b/servers/boe-server-case/src/main/resources/application-pre.properties @@ -1,9 +1,3 @@ -## redis -spring.redis.database=2 -spring.redis.host=127.0.0.1 -spring.redis.password=ENC(zA5LNV8xw3yEx6LMwdGGBGgNsOaD3Cg+) -spring.redis.port=6379 - ## datasource config spring.datasource.driverClassName=com.mysql.jdbc.Driver # spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver @@ -14,19 +8,10 @@ spring.datasource.password=ENC(lAoFOYuc8CAypPtigTNLYg==) logging.level.org.hibernate.SQL=ERROR # logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE -## 静态文件目录,默认是在static下面,以后独立到nginx下面配置 -spring.web.resources.static-locations=file:E:/Projects/BOE/java/static ## xboe config xboe.api.cross_filter=true -## 上传相磁的路径配置 -xboe.upload.file.temp_path=E:/Projects/BOE/java/static/temp -xboe.upload.file.save_path=E:/Projects/BOE/java/static/upload -xboe.upload.file.http_path=http://localhost:9090/cdn/upload - -## 外部接口调用地址 旧系统机构及用户数据接口 -xboe.externalinterface.url.system=http://localhost:9091 ## 案例接口配置 ## 案例接口配置,init 是否需要初始化 diff --git a/servers/boe-server-case/src/main/resources/application-pro.properties b/servers/boe-server-case/src/main/resources/application-pro.properties index 4423487d..a4b6c6cb 100644 --- a/servers/boe-server-case/src/main/resources/application-pro.properties +++ b/servers/boe-server-case/src/main/resources/application-pro.properties @@ -1,10 +1,6 @@ -## redis -spring.redis.database=2 -spring.redis.host=127.0.0.1 -spring.redis.password=ENC(zA5LNV8xw3yEx6LMwdGGBGgNsOaD3Cg+) -spring.redis.port=6379 - ## datasource config +spring.jpa.show-sql = false +spring.jpa.hibernate.ddl-auto=none spring.datasource.driverClassName=com.mysql.jdbc.Driver # spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boe_base?useSSL=false&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull @@ -14,19 +10,10 @@ spring.datasource.password=ENC(lAoFOYuc8CAypPtigTNLYg==) logging.level.org.hibernate.SQL=ERROR # logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE -## 静态文件目录,默认是在static下面,以后独立到nginx下面配置 -spring.web.resources.static-locations=file:E:/Projects/BOE/java/static ## xboe config xboe.api.cross_filter=true -## 上传相磁的路径配置 -xboe.upload.file.temp_path=E:/Projects/BOE/java/static/temp -xboe.upload.file.save_path=E:/Projects/BOE/java/static/upload -xboe.upload.file.http_path=http://localhost:9090/cdn/upload - -## 外部接口调用地址 旧系统机构及用户数据接口 -xboe.externalinterface.url.system=http://localhost:9091 ## 案例接口配置 ## 案例接口配置,init 是否需要初始化 diff --git a/servers/boe-server-case/src/main/resources/application-test.properties b/servers/boe-server-case/src/main/resources/application-test.properties index 974e46b6..e8b04054 100644 --- a/servers/boe-server-case/src/main/resources/application-test.properties +++ b/servers/boe-server-case/src/main/resources/application-test.properties @@ -1,30 +1,17 @@ -## redis -spring.redis.database=2 -spring.redis.host=127.0.0.1 -spring.redis.password=ENC(zA5LNV8xw3yEx6LMwdGGBGgNsOaD3Cg+) -spring.redis.port=6379 - ## datasource config +spring.jpa.show-sql = false +spring.jpa.hibernate.ddl-auto=none spring.datasource.driverClassName=com.mysql.jdbc.Driver # spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boe_base?useSSL=false&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull spring.datasource.username=boe_base spring.datasource.password=ENC(MaC28GJw2JcbH8Lil0CrqSDTYxX49FJ0rxcmHH2pX0k=) -logging.level.org.hibernate.SQL=DEBUG -logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE ## xboe config xboe.api.cross_filter=true -## 上传相磁的路径配置 -xboe.upload.file.temp_path=/www/wwwroot/file/temp -xboe.upload.file.save_path=/www/wwwroot/file/upload -xboe.upload.file.http_path=http://114.115.162.187/file/upload - -## 外部接口调用地址 旧系统机构及用户数据接口 -xboe.externalinterface.url.system=http://127.0.0.1:9091 ## 案例接口配置 ## 案例接口配置,init 是否需要初始化 diff --git a/servers/boe-server-case/src/main/resources/application.properties b/servers/boe-server-case/src/main/resources/application.properties index 59fd44d4..25721cb4 100644 --- a/servers/boe-server-case/src/main/resources/application.properties +++ b/servers/boe-server-case/src/main/resources/application.properties @@ -31,10 +31,6 @@ spring.servlet.multipart.max-request-size=1024MB ## 静态文件目录,默认是在static下面,以后独立到nginx下面配置 spring.mvc.static-path-pattern=/cdn/** -spring.redis.database=2 -spring.redis.host=127.0.0.1 -spring.redis.password=1Qaz2wsx -spring.redis.port=6379 spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.min-idle=0 @@ -47,10 +43,10 @@ spring.redis.lettuce.shutdown-timeout=100ms # jpa config spring.jpa.database = MYSQL -spring.jpa.show-sql = true +spring.jpa.show-sql = false # spring.jpa.properties.hibernate.cache.use_second_level_cache=true # spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect