Fork me on GitHub

Architecture

This guide explains the architecture and module structure of liboscal-java.

liboscal-java is built on top of the Metaschema Java Tools framework. It provides OSCAL-specific functionality while leveraging the general-purpose Metaschema capabilities for serialization, validation, and querying.

Understanding the architecture helps you work effectively with the library, especially when debugging issues or extending its capabilities. The library follows a layered design where each layer builds on the capabilities provided by the layer below.

This section describes how the library is organized and relates to its dependencies.

The following diagram shows how liboscal-java relates to the underlying Metaschema framework and the OSCAL model definitions:

liboscal-java
    │
    ├── metaschema-databind (data binding, serialization)
    │   ├── metaschema-core (Metaschema model, Metapath)
    │   └── metaschema-model (module loading)
    │
    └── OSCAL Metaschema modules (generated model classes)
        ├── oscal_catalog
        ├── oscal_profile
        ├── oscal_mapping
        ├── oscal_ssp
        ├── oscal_component-definition
        ├── oscal_assessment-plan
        ├── oscal_assessment-results
        └── oscal_poam

The library's Java packages are organized by functionality. The main entry point is OscalBindingContext, and all OSCAL model classes live under the model subpackage:

dev.metaschema.oscal.lib
├── OscalBindingContext       # Central entry point
├── model/                    # Generated OSCAL model classes
│   ├── Catalog
│   ├── Profile
│   ├── MappingCollection
│   ├── SystemSecurityPlan
│   ├── ComponentDefinition
│   ├── AssessmentPlan
│   ├── AssessmentResults
│   └── PlanOfActionAndMilestones
├── profile/
│   └── resolver/             # Profile resolution
│       └── ProfileResolver
└── metapath/
    └── function/
        └── library/          # OSCAL-specific Metapath functions
            └── OscalFunctionLibrary

The following sections describe the main classes you'll interact with when using the library.

The OscalBindingContext is your starting point for all OSCAL operations. It provides factory methods for creating serializers, deserializers, and validators:

OscalBindingContext context = OscalBindingContext.instance();

// Create readers/writers
IDeserializer<Catalog> reader = context.newDeserializer(Format.JSON, Catalog.class);
ISerializer<Catalog> writer = context.newSerializer(Format.JSON, Catalog.class);

// Access Metapath support
StaticContext staticCtx = context.getStaticContext();

Responsibilities:

  • Load OSCAL Metaschema modules
  • Register OSCAL-specific Metapath functions
  • Provide serialization/deserialization factories
  • Manage constraint validation

Rather than hand-writing Java classes for each OSCAL element, the library generates them from the official OSCAL Metaschema definitions. This ensures the Java model always matches the OSCAL specification:

oscal/src/main/metaschema/
    ├── oscal_catalog_metaschema.xml
    ├── oscal_profile_metaschema.xml
    └── ...
        ↓ (maven-metaschema-plugin)
target/generated-sources/metaschema/
    └── dev/metaschema/oscal/lib/model/
        ├── Catalog.java
        ├── Profile.java
        └── ...

One of the most important OSCAL operations is profile resolution—converting a profile (which references controls in external catalogs) into a standalone resolved catalog. The ProfileResolver class handles this:

ProfileResolver resolver = new ProfileResolver();
Catalog resolved = resolver.resolve(profile);

Resolution steps:

  1. Load imported catalogs/profiles
  2. Apply control selections
  3. Apply modifications
  4. Merge controls from multiple imports
  5. Generate resolved catalog

The library extends the base Metapath expression language with OSCAL-specific functions. These are automatically registered when you use OscalBindingContext:

Function Purpose
has-oscal-namespace Check namespace membership
resolve-profile Resolve profile imports
resolve-reference Resolve internal references

Understanding how data flows through the system helps when debugging issues or optimizing performance. This section traces the path of data through the major operations.

When you deserialize an OSCAL document, the library detects the format, parses the content, and maps it to Java objects:

File/URL
    ↓
Format Detection (XML/JSON/YAML)
    ↓
Parser (Jackson/StAX)
    ↓
Metaschema Databind (unmarshalling)
    ↓
Generated Model Objects
    ↓
Optional: Constraint Validation
    ↓
Application Code

Serialization is the reverse process—converting your Java objects back to XML, JSON, or YAML:

Model Objects
    ↓
Metaschema Databind (marshalling)
    ↓
Serializer (Jackson/StAX)
    ↓
Format Output (XML/JSON/YAML)
    ↓
File/Stream

Profile resolution is more complex because it may involve loading external resources and applying multiple transformations:

Profile (with imports)
    ↓
Load Referenced Catalogs/Profiles (recursive)
    ↓
Apply Selections (include/exclude)
    ↓
Apply Modifications (parameters, alterations)
    ↓
Merge Controls
    ↓
Resolved Catalog

This section describes how the library integrates with the Metaschema ecosystem and how you can extend it.

liboscal-java extends the base Metaschema framework with OSCAL-specific functionality. The following table shows how the library maps to Metaschema components:

Metaschema Component liboscal-java Usage
IBindingContext OscalBindingContext extends it
IDeserializer Load OSCAL documents
ISerializer Write OSCAL documents
MetapathExpression Query OSCAL content
IConstraintValidationHandler Validate constraints

The library provides hooks for adding custom functionality without modifying the core code.

You can extend the Metapath query language with domain-specific functions. This is useful when you need operations that aren't provided by the built-in function library:

// Register custom function
context.registerFunction(myCustomFunction);

To customize how constraint violations are reported or handled, implement your own validation handler. This allows integration with logging frameworks, custom error reporting, or workflow systems:

IConstraintValidationHandler handler = new MyHandler();
deserializer.setConstraintValidationHandler(handler);

This section covers topics important when deploying applications that use the library.

When using the library in multi-threaded applications, understanding which components are thread-safe helps avoid subtle bugs. The general pattern is to share the context but create fresh serializers and deserializers for each operation:

  • OscalBindingContext.instance() is thread-safe for reading configuration
  • Deserializers are single-use (create new per operation)
  • Serializers are single-use (create new per operation)
  • Model objects are not thread-safe for concurrent modification

The following example shows the recommended pattern for parallel processing:

// Good: Shared context, per-operation deserializers
OscalBindingContext context = OscalBindingContext.instance();

executor.submit(() -> {
    IDeserializer<Catalog> reader = context.newDeserializer(Format.JSON, Catalog.class);
    Catalog catalog = reader.deserialize(path);
    // process catalog
});

OSCAL documents can vary significantly in size, from small component definitions to comprehensive catalogs like NIST SP 800-53. Keep these factors in mind when working with larger documents:

  • Large catalogs (SP 800-53) can consume significant memory when fully loaded
  • Consider streaming approaches for very large documents if you only need to process portions
  • Profile resolution loads the entire import chain into memory, which can be substantial for profiles with deep import hierarchies

The library uses Maven for its build process. A key step is the automatic generation of Java model classes from the OSCAL Metaschema definitions. This ensures the Java API always matches the official OSCAL specification:

1. Compile Metaschema Sources
   ↓
2. Generate Java Model Classes (maven-metaschema-plugin)
   ↓
3. Compile Generated + Handwritten Java
   ↓
4. Run Tests
   ↓
5. Package JAR

If you're building from source, the generated classes appear in target/generated-sources/metaschema/ and are automatically included in the compilation.

liboscal-java is part of a larger ecosystem of tools for working with OSCAL and Metaschema. Understanding these relationships helps when you need to trace issues or find additional capabilities:

Project Relationship
metaschema-java Core framework dependency
oscal-cli CLI built on liboscal-java
OSCAL Source Metaschema definitions

Continue learning about liboscal-java with these related guides: