Audits & consulting →
I do one-week, fixed-price code audits for B2B SaaS companies on AWS:
- Multi-Tenant SaaS on AWS: architecture, cost, and SOC 2 alignment
- AI-on-AWS for Regulated SaaS: Bedrock, RAG, and guardrails under SOC 2, HIPAA, or 21 CFR Part 11
Fixed price, written report, walkthrough call. Details at audits.putney.io.
A production-ready Java 17 library for parsing and validating SCORM 1.2, SCORM 2004, AICC, cmi5, and xAPI/TinCan modules. The parser normalizes metadata across formats, making it simple to integrate new eLearning packages into LMS platforms, content pipelines, and QA tooling.
- Auto-detects SCORM 1.2, SCORM 2004 (editions 2-4), AICC, cmi5, and xAPI/TinCan manifests via pluggable detectors
- Produces typed metadata (
Scorm2004Metadata,Scorm12Metadata,AiccMetadata,Cmi5Metadata,XapiMetadata) with consistent accessors for titles, launch URLs, structure, and mastery data - Uses storage-agnostic
FileAccessstrategies (local directories, ZIP files, AWS S3 SDK v1/v2, in-memory payloads, and cached wrappers) - Emits rich domain exceptions (
ModuleDetectionException,ManifestParseException,ModuleParsingException) for graceful error handling - Supports progress and event listeners for observability, plus optional SCORM 2004 schema validation
- Backed by extensive unit, integration, and property-based tests with real-world fixtures
The library is published to Maven Central.
<dependency>
<groupId>dev.jcputney</groupId>
<artifactId>elearning-module-parser</artifactId>
<version>1.0.0</version>
</dependency>implementation("dev.jcputney:elearning-module-parser:1.0.0")Add one of the AWS SDKs to enable S3-backed storage:
<!-- Recommended: AWS SDK v2 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.32.5</version>
</dependency>
<!-- Alternatively: AWS SDK v1 -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.788</version>
</dependency>try(ZipFileAccess access = new ZipFileAccess("path/to/module.zip")) {
ModuleParserFactory factory = new DefaultModuleParserFactory(access);
ModuleParser<?> parser = factory.getParser();
ModuleMetadata<?> metadata = parser.parse();
System.out.println("Module Type: "+metadata.getModuleType());
System.out.println("Title: "+metadata.getTitle());
System.out.println("Launch URL: "+metadata.getLaunchUrl());
}- SCORM 1.2: detected via
imsmanifest.xmlusing the SCORM 1.2 namespace; exposes the organization tree, prerequisites, and mastery data. - SCORM 2004: detected via
imsmanifest.xmlwith SCORM 2004 namespaces; surfaces edition details, sequencing, rollup rules, and delivery controls. - AICC: detected by a
.crsdescriptor containing a[Course]section; parses assignable units and prerequisite graphs. - cmi5: detected by the presence of
cmi5.xml; provides AU metadata, move-on criteria, and launch parameters. - xAPI/TinCan: detected by the presence of
tincan.xml; parses activity definitions, launch URLs, and metadata for Tin Can API packages.
// ZIP archives
try(ZipFileAccess zip = new ZipFileAccess("module.zip")) {
ModuleMetadata<?> metadata = new DefaultModuleParserFactory(zip).parseModule();
}
// Extracted directories
try(LocalFileAccess local = new LocalFileAccess("/path/to/folder")) {
ModuleMetadata<?> metadata = new DefaultModuleParserFactory(local).parseModule();
}
// AWS S3 (SDK v2)
S3FileAccessV2 s3 = new S3FileAccessV2(s3Client, "bucket", "prefix/");
ModuleMetadata<?> metadata = new DefaultModuleParserFactory(s3).parseModule();Available FileAccess implementations include LocalFileAccess, ZipFileAccess, S3FileAccessV1,
S3FileAccessV2, ClasspathFileAccess, InMemoryFileAccess, and CachedFileAccess. You can also
implement FileAccess for custom backends.
ModuleMetadata<?> metadata = factory.parseModule();
System.out.println(metadata.getTitle());
System.out.println(metadata.getDuration());
System.out.println(metadata.getSizeOnDisk());
switch(metadata.getModuleType()) {
case SCORM_2004 -> {
Scorm2004Metadata scorm = (Scorm2004Metadata) metadata;
scorm.getActivityDeliveryControls().forEach((id, controls) -> {
// Inspect sequencing and navigation controls
});
}
case AICC -> {
AiccMetadata aicc = (AiccMetadata) metadata;
System.out.println(aicc.getPrerequisitesGraph());
}
case CMI5 ->{
Cmi5Metadata cmi5 = (Cmi5Metadata) metadata;
cmi5.getAuDetails().forEach(au -> System.out.println(au.getLaunchMethod()));
}
case SCORM_12 ->{
Scorm12Metadata scorm12 = (Scorm12Metadata) metadata;
System.out.println(scorm12.getMasteryScores());
}
case XAPI ->{
XapiMetadata xapi = (XapiMetadata) metadata;
xapi.getActivities().forEach(activity -> System.out.println(activity.getName()));
}
}All metadata objects now provide additional helper methods for better LMS integration:
ModuleMetadata<?> metadata = factory.parseModule();
// Check if module has multiple launchable units (affects navigation UI)
boolean hasMultipleUnits = metadata.hasMultipleLaunchableUnits();
// Get the manifest filename
String manifestFile = metadata.getManifestFile();
// Module type and edition information
ModuleType type = metadata.getModuleType();
ModuleEditionType edition = metadata.getEditionType();The hasMultipleLaunchableUnits() method is particularly useful for LMS player configuration, as it
indicates whether navigation controls and table of contents should be displayed:
- SCORM 1.2: Multiple resources with
scormType="sco" - SCORM 2004: Multiple items in organization
- AICC: Multiple assignable units
- cmi5/xAPI: Always
false(single AU/activity)
Use CachedFileAccess to cache file reads and improve performance when parsing modules repeatedly:
try(ZipFileAccess zip = new ZipFileAccess("module.zip")){
CachedFileAccess cached = new CachedFileAccess(zip);
ModuleMetadata<?> metadata = new DefaultModuleParserFactory(cached).parseModule();
// Subsequent operations will use cached data
Map<String, Object> stats = cached.getCacheStatistics();
System.out.println("Cache hit ratio: "+stats.get("hitRatio"));
}Register additional detection plugins or parsers without touching the core pipeline:
ModuleTypeDetector detector = new DefaultModuleTypeDetector(fileAccess);
detector.registerPlugin(new ModuleTypeDetectorPlugin() {
@Override public int getPriority () {
return 100;
}
@Override public ModuleType detect (FileAccess access){
return access.fileExists("custom-manifest.xml") ? ModuleType.CUSTOM : null;
}
});
DefaultModuleParserFactory factory = new DefaultModuleParserFactory(fileAccess, detector);
factory.registerParser(ModuleType.CUSTOM, CustomModuleParser::new);- Exceptions are typed to help you distinguish detection, manifest, and parsing failures.
- SCORM 2004 XSD validation is disabled by default. Enable it via the system property
elearning.parser.scorm2004.validateXsd=trueor the environment variableELEARNING_SCORM2004_VALIDATE_XSD=true. - Use
CachedFileAccessto de-duplicate I/O when the same module is parsed repeatedly.
Use the Maven wrapper to build and test:
./mvnw clean compile
./mvnw test
./mvnw verify
./mvnw test jacoco:reportGuidelines:
- Requires Java 17+
- Follows the repository
.editorconfig(2-space indentation, 100-character lines) - Tests use JUnit 5 and jqwik; integration fixtures live under
src/test/resources/modules - Pull requests should include updated tests and pass CI
- Advanced Distributed Learning (ADL) for SCORM and cmi5 specifications
- Aviation Industry CBT Committee (AICC) for the AICC standard
- IMS Global Learning Consortium for content packaging guidance
This project is licensed under the Apache License, Version 2.0. See LICENSE and NOTICE for full details.
Versions 0.4.x and earlier were released under the GNU Lesser General Public License v3.0. Those releases remain available under their original LGPL-3.0 terms via Maven Central; relicensing applies to version 1.0.0 and later.