Skip to content

refactor: introduce IControlSelection/IControlMatching common interfaces#281

Merged
david-waltermire merged 5 commits into
metaschema-framework:developfrom
david-waltermire:fix/control-selection-common-interface
Apr 23, 2026
Merged

refactor: introduce IControlSelection/IControlMatching common interfaces#281
david-waltermire merged 5 commits into
metaschema-framework:developfrom
david-waltermire:fix/control-selection-common-interface

Conversation

@david-waltermire
Copy link
Copy Markdown
Contributor

@david-waltermire david-waltermire commented Apr 23, 2026

Summary

OSCAL's `select-control-by-id` and `matching` assemblies are defined locally across multiple modules with two distinct shapes:

  • Shared shape (`with-child-controls`, `with-ids`, `matching`): in control-common, profile, and mapping-common. The generator consolidates these into single classes.
  • Assessment shape (`control-id`, `statement-ids`; no matching): in assessment-common, generated as a separate class.

This PR makes the module relationship explicit in generated class names, introduces common interfaces, and places hand-written types in packages that match their scope.

Changes

New common interfaces in `dev.metaschema.oscal.lib.model.control`:

  • `IControlSelection` — `getWithChildControls`, `getWithIds`, `getMatching()` returning `List<? extends IControlMatching>`
  • `IControlMatching` — `getPattern`

Generated class renames (in binding config):

  • `ProfileSelectControlById` → `ControlCommonSelectControlById` (used by profile's `InsertControls` and mapping's `GapSummary`, generated from `oscal_control-common_metaschema.xml`)
  • `SelectControlById` → `AssessmentCommonSelectControlById` (used by `ReviewedControls`, new binding added for `oscal_assessment-common_metaschema.xml`)

Hand-written class renames + moves (lockstep with the generated renames):

  • `IProfileSelectControlById` → `IControlCommonSelectControlById`, moved up to `dev.metaschema.oscal.lib.model.control` (now extends `IControlSelection` with covariant `getMatching()` returning `List`)
  • `AbstractProfileSelectControlById` → `AbstractControlCommonSelectControlById`, moved up to `dev.metaschema.oscal.lib.model.control`
  • New `AbstractControlMatching` in `dev.metaschema.oscal.lib.model.control`, implements `IControlMatching`, wired to the `matching` binding via `extend-base-class` (replaces the short-lived `AbstractProfileMatching`)

Consumer references updated in `IControlFilter`, `DefaultControlSelectionFilter`, and their tests.

Test plan

  • `mvn compile` succeeds
  • `mvn test` — 46 tests run, 0 failures, 0 errors, 3 skipped
  • CI: Code, CodeQL, Trivy, Website

Summary by CodeRabbit

  • New Features

    • Added a common control selection/matching contract allowing selection rules to include pattern-based matching.
  • Refactor

    • Consolidated profile-specific control selection into a shared control module so selection logic is reusable across components.
    • Updated public APIs to consume the new common selection interfaces.
  • Tests

    • Adjusted tests to exercise the new common selection and matching contracts.

OSCAL's select-control-by-id and matching assemblies have the same shape
in both the profile and mapping-common modules, and the generator
consolidates them into a single ProfileMatching/ProfileSelectControlById
class. Introduce common interfaces so consumer code can use a neutral
type instead of profile-specific names:

- IControlSelection: getWithChildControls, getWithIds, getMatching
- IControlMatching: getPattern
- IProfileSelectControlById now extends IControlSelection with a
  covariant getMatching() returning List<ProfileMatching>
- AbstractProfileMatching implements IControlMatching, wired to the
  matching binding via extend-base-class
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a89697c8-7830-4f41-aa04-ff733acca562

📥 Commits

Reviewing files that changed from the base of the PR and between 43f5507 and 315aa28.

📒 Files selected for processing (2)
  • src/main/java/dev/metaschema/oscal/lib/model/control/AbstractControlMatching.java
  • src/main/java/dev/metaschema/oscal/lib/model/control/IControlMatching.java
✅ Files skipped from review due to trivial changes (2)
  • src/main/java/dev/metaschema/oscal/lib/model/control/AbstractControlMatching.java
  • src/main/java/dev/metaschema/oscal/lib/model/control/IControlMatching.java

📝 Walkthrough

Walkthrough

Adds common control-selection abstractions (IControlSelection, IControlMatching, AbstractControlMatching) and migrates profile-specific select-by-id types into a control-common API, updating metaschema bindings, selection filter types, and related tests to use the new common interfaces and classes.

Changes

Cohort / File(s) Summary
New Selection Abstractions
src/main/java/dev/metaschema/oscal/lib/model/control/IControlMatching.java, src/main/java/dev/metaschema/oscal/lib/model/control/IControlSelection.java
New public interfaces: IControlMatching (getPattern) and IControlSelection (getWithChildControls, getWithIds, getMatching).
Abstract Matching & Select-by-id
src/main/java/dev/metaschema/oscal/lib/model/control/AbstractControlMatching.java, src/main/java/dev/metaschema/oscal/lib/model/control/AbstractControlCommonSelectControlById.java
Adds AbstractControlMatching and moves/refactors the profile-specific abstract select-by-id into AbstractControlCommonSelectControlById with updated builder return types.
Common Select Interface
src/main/java/dev/metaschema/oscal/lib/model/control/IControlCommonSelectControlById.java
Introduces IControlCommonSelectControlById replacing the old profile-specific interface and now extending IControlSelection (returns List<ProfileMatching> for getMatching).
Selection Filter & API Usage
src/main/java/dev/metaschema/oscal/lib/profile/resolver/selection/DefaultControlSelectionFilter.java, src/main/java/dev/metaschema/oscal/lib/profile/resolver/selection/IControlFilter.java
Type usages updated to consume IControlCommonSelectControlById instead of profile-specific interface in constructors and filter construction logic.
Metaschema Bindings
src/main/metaschema-bindings/oscal-metaschema-bindings.xml
Updates Java class mappings for select-control-by-id and matching to control-common classes and adds assessment-common select-control-by-id binding.
Tests Updated
src/test/java/.../DefaultControlSelectionFilterTest.java, src/test/java/.../ImportTest.java
Mocks and test builders updated to use IControlCommonSelectControlById / AbstractControlCommonSelectControlById in place of profile-specific types.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • aj-stein

Poem

🐰
I hopped through interfaces, light and quick,
Planting patterns, matching each pick.
From profile burrows to common air,
Selection seeds scattered everywhere.
Code-bunny smiles — bindings repaired. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: introducing two new common interfaces (IControlSelection and IControlMatching) to refactor control selection logic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/java/dev/metaschema/oscal/lib/model/control/IControlSelection.java (1)

14-16: Clarify nullability/emptiness contract for collection accessors.

Line [16] is consumed with defensive null handling in DefaultControlSelectionFilter (Line [149]). Consider explicitly documenting whether these methods can return null vs empty lists (especially Line [14] and Line [16]) to make the interface contract unambiguous.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/dev/metaschema/oscal/lib/model/control/IControlSelection.java`
around lines 14 - 16, Document and enforce a clear nullability contract for
IControlSelection collection accessors: update the interface method declarations
for getWithIds() and getMatching() to specify (via Javadoc and/or annotations
such as `@NonNull/`@Nullable) whether they may return null or must return an empty
list when absent, and update implementations (including
DefaultControlSelectionFilter) to comply — preferably make them non-null by
returning Collections.emptyList() when there are no elements and annotate
Javadoc/@NonNull on getWithIds() and getMatching() so callers know they can
avoid defensive null checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/dev/metaschema/oscal/lib/model/control/IControlSelection.java`:
- Around line 14-16: Document and enforce a clear nullability contract for
IControlSelection collection accessors: update the interface method declarations
for getWithIds() and getMatching() to specify (via Javadoc and/or annotations
such as `@NonNull/`@Nullable) whether they may return null or must return an empty
list when absent, and update implementations (including
DefaultControlSelectionFilter) to comply — preferably make them non-null by
returning Collections.emptyList() when there are no elements and annotate
Javadoc/@NonNull on getWithIds() and getMatching() so callers know they can
avoid defensive null checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6aea5c10-67e9-4b96-aeb0-517243ea5d75

📥 Commits

Reviewing files that changed from the base of the PR and between b41b0fb and a865bf4.

📒 Files selected for processing (5)
  • src/main/java/dev/metaschema/oscal/lib/model/control/IControlMatching.java
  • src/main/java/dev/metaschema/oscal/lib/model/control/IControlSelection.java
  • src/main/java/dev/metaschema/oscal/lib/model/control/profile/AbstractProfileMatching.java
  • src/main/java/dev/metaschema/oscal/lib/model/control/profile/IProfileSelectControlById.java
  • src/main/metaschema-bindings/oscal-metaschema-bindings.xml

Rename the generated select-control-by-id classes in the binding config
to reflect which OSCAL module each originates from, making the
profile/mapping vs assessment distinction explicit:

- ProfileSelectControlById -> ControlCommonSelectControlById (used by
  profile's InsertControls and mapping's GapSummary; generated from
  oscal_control-common_metaschema.xml)
- SelectControlById -> AssessmentCommonSelectControlById (used by
  assessment-plan/assessment-results' ReviewedControls; generated from
  oscal_assessment-common_metaschema.xml, added as a new binding)

Rename the hand-written supporting types in lockstep:
- IProfileSelectControlById -> IControlCommonSelectControlById
- AbstractProfileSelectControlById -> AbstractControlCommonSelectControlById

Consumer references updated in IControlFilter,
DefaultControlSelectionFilter, and their tests.
IControlCommonSelectControlById and AbstractControlCommonSelectControlById
are no longer profile-specific (they cover control-common-defined types
shared between profile and mapping-common). Move them up from
dev.metaschema.oscal.lib.model.control.profile to
dev.metaschema.oscal.lib.model.control so the package placement matches
the naming.

Updates the binding's extend-base-class reference and all consumer
imports.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/metaschema-bindings/oscal-metaschema-bindings.xml`:
- Around line 162-169: The select-control-by-id metaschema binding must extend a
base class so the generated AssessmentCommonSelectControlById implements the
module-agnostic IControlSelection; update the <define-assembly-binding
name="select-control-by-id"> for the assessment metaschema to include an
extend-base-class that references AbstractAssessmentCommonSelectControlById
(mirroring the control-common pattern where
AbstractControlCommonSelectControlById implements
IControlCommonSelectControlById which extends IControlSelection), ensuring
AssessmentCommonSelectControlById implements the expected interface and
preserves polymorphic usage across modules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 821a8f13-25d0-470d-abf0-76c346bb0876

📥 Commits

Reviewing files that changed from the base of the PR and between a865bf4 and de7ec52.

📒 Files selected for processing (7)
  • src/main/java/dev/metaschema/oscal/lib/model/control/profile/AbstractControlCommonSelectControlById.java
  • src/main/java/dev/metaschema/oscal/lib/model/control/profile/IControlCommonSelectControlById.java
  • src/main/java/dev/metaschema/oscal/lib/profile/resolver/selection/DefaultControlSelectionFilter.java
  • src/main/java/dev/metaschema/oscal/lib/profile/resolver/selection/IControlFilter.java
  • src/main/metaschema-bindings/oscal-metaschema-bindings.xml
  • src/test/java/dev/metaschema/oscal/lib/profile/resolver/selection/DefaultControlSelectionFilterTest.java
  • src/test/java/dev/metaschema/oscal/lib/profile/resolver/selection/ImportTest.java
✅ Files skipped from review due to trivial changes (1)
  • src/test/java/dev/metaschema/oscal/lib/profile/resolver/selection/ImportTest.java

Comment on lines +162 to +169
<metaschema-binding
href="../../../oscal/src/metaschema/oscal_assessment-common_metaschema.xml">
<define-assembly-binding name="select-control-by-id">
<java>
<use-class-name>AssessmentCommonSelectControlById</use-class-name>
</java>
</define-assembly-binding>
</metaschema-binding>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's an AbstractAssessmentCommonSelectControlById or similar base class defined
fd -t f -e java -i "selectcontrolbyid" src/main/java | xargs -I {} grep -l "Assessment" {} 2>/dev/null

# Check the OSCAL assessment-common metaschema structure to understand the select-control-by-id definition
rg -n "select-control-by-id" --glob "*.xml" oscal/src/metaschema/ 2>/dev/null | head -20

Repository: metaschema-framework/liboscal-java

Length of output: 60


🏁 Script executed:

# First, let's check the structure of the repository
git ls-files | grep -E "(metaschema-bindings|IControlSelection)" | head -20

Repository: metaschema-framework/liboscal-java

Length of output: 401


🏁 Script executed:

# Let's read the actual file mentioned in the review to understand the context
wc -l src/main/metaschema-bindings/oscal-metaschema-bindings.xml

Repository: metaschema-framework/liboscal-java

Length of output: 140


🏁 Script executed:

# Read the relevant section and surrounding context
sed -n '150,180p' src/main/metaschema-bindings/oscal-metaschema-bindings.xml

Repository: metaschema-framework/liboscal-java

Length of output: 1330


🏁 Script executed:

# Search for IControlSelection interface
fd -t f -name "*.java" | xargs grep -l "interface IControlSelection" 2>/dev/null

Repository: metaschema-framework/liboscal-java

Length of output: 310


🏁 Script executed:

# Search for control-common binding to compare
grep -n "control-common" src/main/metaschema-bindings/oscal-metaschema-bindings.xml | head -10

Repository: metaschema-framework/liboscal-java

Length of output: 156


🏁 Script executed:

# Let's see the control-common binding definition
sed -n '47,120p' src/main/metaschema-bindings/oscal-metaschema-bindings.xml

Repository: metaschema-framework/liboscal-java

Length of output: 2976


🏁 Script executed:

# Read the IControlSelection interface
cat src/main/java/dev/metaschema/oscal/lib/model/control/IControlSelection.java

Repository: metaschema-framework/liboscal-java

Length of output: 381


🏁 Script executed:

# Search for other assessment-related interfaces that might be relevant
fd -t f -name "*.java" -path "*/assessment*" | xargs grep -l "interface.*Assessment.*Select" 2>/dev/null

Repository: metaschema-framework/liboscal-java

Length of output: 310


🏁 Script executed:

# Look for any existing AssessmentCommonSelectControlById references
rg "AssessmentCommonSelectControlById" --type java

Repository: metaschema-framework/liboscal-java

Length of output: 60


🏁 Script executed:

# Check if there's an AbstractControlCommonSelectControlById class
fd -t f -name "*SelectControlById*" src/main/java

Repository: metaschema-framework/liboscal-java

Length of output: 308


🏁 Script executed:

# Find AbstractControlCommonSelectControlById
fd -t f -name "*AbstractControlCommonSelectControlById*" src/main/java

Repository: metaschema-framework/liboscal-java

Length of output: 308


🏁 Script executed:

# Search for the class definition
rg "class AbstractControlCommonSelectControlById" --type java -A 5

Repository: metaschema-framework/liboscal-java

Length of output: 912


🏁 Script executed:

# Check if it implements IControlSelection
rg "AbstractControlCommonSelectControlById" --type java -A 10 | head -30

Repository: metaschema-framework/liboscal-java

Length of output: 3470


🏁 Script executed:

# Look for any assessment module base classes that might be models
fd -t f -path "*/assessment*" -name "Abstract*.java" src/main/java

Repository: metaschema-framework/liboscal-java

Length of output: 246


🏁 Script executed:

# Search for the actual generated or expected class pattern in assessment module
rg "AssessmentCommon" --type java | head -20

Repository: metaschema-framework/liboscal-java

Length of output: 60


🏁 Script executed:

# Find IControlCommonSelectControlById interface
rg "interface IControlCommonSelectControlById" --type-list

Repository: metaschema-framework/liboscal-java

Length of output: 467


🏁 Script executed:

# Use correct search for the interface
rg "interface IControlCommonSelectControlById"

Repository: metaschema-framework/liboscal-java

Length of output: 244


🏁 Script executed:

# Examine the interface
cat src/main/java/dev/metaschema/oscal/lib/model/control/IControlCommonSelectControlById.java

Repository: metaschema-framework/liboscal-java

Length of output: 414


🏁 Script executed:

# Check if AbstractControlCommonSelectControlById has any relationship with IControlSelection
rg "IControlSelection" src/main/java/dev/metaschema/oscal/lib/model/control/AbstractControlCommonSelectControlById.java

Repository: metaschema-framework/liboscal-java

Length of output: 60


🏁 Script executed:

# Look for what IControlCommonSelectControlById extends or implements
rg -A 3 "interface IControlCommonSelectControlById"

Repository: metaschema-framework/liboscal-java

Length of output: 566


🏁 Script executed:

# Check if there are any assessment-related selection classes
rg "assessment.*select" -i src/main/metaschema-bindings/oscal-metaschema-bindings.xml

Repository: metaschema-framework/liboscal-java

Length of output: 148


🏁 Script executed:

# Check if AbstractAssessmentCommonSelectControlById exists
find src/main/java -name "*AssessmentCommon*" -type f

Repository: metaschema-framework/liboscal-java

Length of output: 60


🏁 Script executed:

# Search for any assessment module select-control references in code
rg "assessment.*SelectControlById" -i

Repository: metaschema-framework/liboscal-java

Length of output: 207


🏁 Script executed:

# Check what other assessment-common bindings look like in the file
rg "oscal_assessment-common" src/main/metaschema-bindings/oscal-metaschema-bindings.xml -A 8

Repository: metaschema-framework/liboscal-java

Length of output: 454


🏁 Script executed:

# Look at the full assessment module bindings pattern
grep -A 6 "oscal_assessment" src/main/metaschema-bindings/oscal-metaschema-bindings.xml | head -60

Repository: metaschema-framework/liboscal-java

Length of output: 962


Add extend-base-class to AssessmentCommonSelectControlById to implement IControlSelection.

The select-control-by-id binding in oscal_assessment-common_metaschema.xml lacks an extend-base-class, unlike its counterpart in oscal_control-common_metaschema.xml. The control-common binding extends AbstractControlCommonSelectControlById, which implements IControlCommonSelectControlById (which extends IControlSelection), enabling polymorphic usage via the module-agnostic interface. Without a corresponding base class for the assessment module, AssessmentCommonSelectControlById won't implement IControlSelection, breaking consistency with PR objectives for module-agnostic interfaces.

Consider creating AbstractAssessmentCommonSelectControlById as the extend-base-class for this binding to maintain interface consistency across modules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/metaschema-bindings/oscal-metaschema-bindings.xml` around lines 162
- 169, The select-control-by-id metaschema binding must extend a base class so
the generated AssessmentCommonSelectControlById implements the module-agnostic
IControlSelection; update the <define-assembly-binding
name="select-control-by-id"> for the assessment metaschema to include an
extend-base-class that references AbstractAssessmentCommonSelectControlById
(mirroring the control-common pattern where
AbstractControlCommonSelectControlById implements
IControlCommonSelectControlById which extends IControlSelection), ensuring
AssessmentCommonSelectControlById implements the expected interface and
preserves polymorphic usage across modules.

The abstract base class applies to the control-common-defined matching
assembly used by both profile and mapping-common, so the profile-specific
name was misleading. Rename and move up to dev.metaschema.oscal.lib.model.control
to match the IControlMatching interface's package and the sibling
AbstractControlCommonSelectControlById placement.
…interface

Satisfies the pmd-verify step in the Website CI job:
- AbstractControlMatching: add protected no-arg constructor (AbstractClassWithoutAnyMethod)
- IControlMatching: add @SuppressWarnings(PMD.ImplicitFunctionalInterface) since the interface is implemented by generated bean classes, not intended as a lambda target
@david-waltermire david-waltermire merged commit 088a7a7 into metaschema-framework:develop Apr 23, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant