You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/06/06 08:21:21 UTC
(causeway) 29/39: CAUSEWAY-4012: limits selector parameters to collection columns
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-4012
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 48d913e1f0cabefa4f0b1844eb495fa9804dcaac
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat Jun 6 06:04:43 2026 +0100
CAUSEWAY-4012: limits selector parameters to collection columns
---
.../specimpl/ObjectSpecificationAbstract.java | 14 +++--
.../ParentedCollectionSelectorActionUtilTest.java | 36 +++++++++++++
.../.openspec.yaml | 2 +
.../design.md | 56 ++++++++++++++++++++
.../proposal.md | 27 ++++++++++
.../parented-collection-selector-actions/spec.md | 59 ++++++++++++++++++++++
.../tasks.md | 18 +++++++
7 files changed, 209 insertions(+), 3 deletions(-)
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index 3d03844074d..8900d79e828 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -50,6 +50,7 @@
import org.apache.causeway.applib.Identifier;
import org.apache.causeway.applib.annotation.Domain;
+import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy;
import org.apache.causeway.applib.fa.FontAwesomeLayers;
import org.apache.causeway.applib.id.LogicalType;
@@ -1064,7 +1065,7 @@ private static ObjectAction create(
final OneToManyAssociation collection) {
val childSpec = collection.getElementType();
- val scalarProperties = scalarPropertiesOf(childSpec);
+ val scalarProperties = scalarPropertiesOf(parentSpec, collection);
val parameterTypes = parameterTypes(parentSpec, scalarProperties);
val parameterNames = parameterNames(parentSpec, scalarProperties);
val actionId = actionIdFor(collection);
@@ -1083,9 +1084,16 @@ private static ObjectAction create(
return ObjectActionDefault.forMethod(facetedMethod);
}
- private static Can<ObjectAssociation> scalarPropertiesOf(final ObjectSpecification childSpec) {
- return childSpec.streamAssociations(MixedIn.INCLUDED)
+ private static Can<ObjectAssociation> scalarPropertiesOf(
+ final ObjectSpecification parentSpec,
+ final OneToManyAssociation collection) {
+ return collection.getElementType()
+ .streamAssociations(MixedIn.INCLUDED)
+ .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(Where.PARENTED_TABLES))
.filter(ParentedCollectionSelectorActionUtil::isEligibleScalarParameterProperty)
+ .sorted(ObjectMember.Comparators
+ .<ObjectAssociation>byMemberOrderSequence(false)
+ .thenComparing(ObjectAssociation::getId))
.collect(Can.toCan());
}
diff --git a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
index 01b64d98144..b01781c2a5c 100644
--- a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
+++ b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
@@ -45,7 +45,9 @@
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.MemberSupport;
import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.PropertyLayout;
import org.apache.causeway.applib.annotation.SemanticsOf;
+import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.applib.services.metamodel.BeanSort;
import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.applib.value.Clob;
@@ -102,6 +104,9 @@ static class LeaseItem {
@Getter
private Clob notes;
@Getter
+ @PropertyLayout(hidden = Where.PARENTED_TABLES)
+ private String internalCode;
+ @Getter
private String logicalTypeName;
@Getter
private String id;
@@ -122,6 +127,22 @@ static class LeaseTerm {
private final String description;
}
+ @DomainObject(nature = Nature.VIEW_MODEL)
+ static class OrderedLease {
+ @Getter
+ private final List<OrderedLeaseItem> items = new ArrayList<>();
+ }
+
+ @DomainObject(nature = Nature.VIEW_MODEL)
+ static class OrderedLeaseItem {
+ @Getter
+ @PropertyLayout(sequence = "2")
+ private String name;
+ @Getter
+ @PropertyLayout(sequence = "1")
+ private Integer sequence;
+ }
+
@RequiredArgsConstructor
@org.apache.causeway.applib.annotation.Collection
static class Lease_mixinItems {
@@ -221,6 +242,7 @@ void exposes_mandatory_parent_parameter_and_optional_scalar_child_parameters() {
assertThat(parameters.getElseFail(0).getElementType().getCorrespondingClass(), is(Lease.class));
assertThat(parameters.stream().anyMatch(parameter -> parameter.getId().equals("name")), is(true));
assertThat(parameters.stream().anyMatch(parameter -> parameter.getId().equals("sequence")), is(true));
+ assertThat(parameters.stream().anyMatch(parameter -> parameter.getId().equals("internalCode")), is(false));
assertThat(parameters.stream().anyMatch(parameter -> parameter.getId().equals("otherLease")), is(false));
assertThat(parameters.stream().anyMatch(parameter -> parameter.getId().equals("terms")), is(false));
assertThat(parameters.stream().anyMatch(parameter -> parameter.getId().equals("attachment")), is(false));
@@ -233,6 +255,20 @@ void exposes_mandatory_parent_parameter_and_optional_scalar_child_parameters() {
assertThat(parameters.stream().anyMatch(parameter -> parameter.getId().equals("datanucleusVersionTimestamp")), is(false));
}
+ @Test
+ void orders_scalar_child_parameters_using_static_collection_column_order() {
+ val orderedMmc = newMetamodelContext();
+ orderedMmc.getConfiguration().getExtensions().getCommandLog().setRecordingSupport(RecordingSupport.ENABLED);
+ val orderedLeaseSpec = orderedMmc.getSpecificationLoader().loadSpecification(OrderedLease.class);
+ val orderedSelectorAction = orderedLeaseSpec.getAction(
+ ObjectSpecificationAbstract.ParentedCollectionSelectorActionUtil.ACTION_ID_PREFIX + "items").orElseThrow();
+ val parameters = orderedSelectorAction.getParameters();
+
+ assertThat(parameters.getElseFail(0).getId(), is("orderedLease"));
+ assertThat(parameters.getElseFail(1).getId(), is("sequence"));
+ assertThat(parameters.getElseFail(2).getId(), is("name"));
+ }
+
@Test
void defaults_parent_parameter_to_action_target() {
val lease = new Lease();
diff --git a/openspec/changes/limit-selector-parameters-to-collection-columns/.openspec.yaml b/openspec/changes/limit-selector-parameters-to-collection-columns/.openspec.yaml
new file mode 100644
index 00000000000..c53ef21aaaf
--- /dev/null
+++ b/openspec/changes/limit-selector-parameters-to-collection-columns/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-06-05
diff --git a/openspec/changes/limit-selector-parameters-to-collection-columns/design.md b/openspec/changes/limit-selector-parameters-to-collection-columns/design.md
new file mode 100644
index 00000000000..e3de1743971
--- /dev/null
+++ b/openspec/changes/limit-selector-parameters-to-collection-columns/design.md
@@ -0,0 +1,56 @@
+## Context
+
+Parented collection selector actions are synthesized from parented collection associations when command-log recording support is enabled.
+The current implementation builds child filter parameters from every eligible scalar property on the collection element type, then applies generic exclusions for non-scalar, blob, clob, and technical metadata properties.
+Collection rendering already has a column model that determines which element members appear as collection columns and in what order.
+The synthetic selector action should mirror that column model so the prompt matches the row-selection surface it records.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Build synthetic selector child filter parameters from the associated collection's visible columns rather than from every eligible scalar property on the child type.
+- Preserve collection column ordering in the synthetic action parameter order after the mandatory parent parameter.
+- Keep the existing scalar, blob, clob, and technical metadata exclusions as a second filter over the collection columns.
+- Use only static metamodel association metadata that is safe during startup introspection.
+- Preserve startup-safe static layout order by using member `LayoutOrderFacet` sequences where available.
+
+**Non-Goals:**
+
+- Do not change the mandatory disabled parent parameter or its position as the first action parameter.
+- Do not add new configuration properties.
+- Do not change collection rendering, row selection, or command DTO shape beyond the set and order of child filter parameters.
+- Do not make reference, collection, blob, clob, or technical metadata properties eligible merely because they appear as collection columns.
+
+## Decisions
+
+- Derive candidate child filter properties from child associations that are statically visible for `Where.PARENTED_TABLES`.
+ This keeps selector prompts aligned with statically rendered collection columns while remaining safe during metamodel startup.
+ The alternative was to call `streamAssociationsForColumnRendering`, but that invokes table-column SPIs that can depend on interaction-scoped services and are not safe during application startup.
+
+- Order child filter properties by `LayoutOrderFacet` sequence when present, falling back to member id for deterministic ordering.
+ This reuses layout sequence metadata already installed on associations from annotations or XML without normalizing grids during selector synthesis.
+ The alternative was to call `GridFacet.getGrid`, but that can recursively trigger selector synthesis while grids are being normalized during startup.
+
+- Apply the existing selector parameter eligibility predicate after resolving the statically column-visible candidates.
+ This preserves current safety and size exclusions while allowing static parented-table visibility metadata to control whether scalar properties are candidates.
+ The alternative was to trust every rendered column, but rendered columns can include members that should not become equality filter parameters.
+
+- Keep the parent parameter outside the column-derived list.
+ The parent parameter represents the selector action target context, not a child column, and existing command export expectations rely on it being present and disabled.
+ The alternative was to remove the parent parameter because it is not a collection column, but that would change command DTO structure and parent scoping semantics.
+
+- Add focused metamodel tests around inclusion, omission, and ordering.
+ Existing tests already exercise selector parameter generation and are the right place to assert that non-column scalar properties are omitted and column order is preserved.
+ The alternative was to rely on viewer tests, but this behavior is synthesized at metamodel level and should be validated without a viewer.
+
+## Risks / Trade-offs
+
+- Runtime column visibility and ordering services can depend on an active interaction, while selector actions are synthesized during metamodel construction.
+ Mitigation: do not invoke `TableColumnVisibilityService`, `TableColumnOrderService`, or grid normalization from selector synthesis; use only startup-safe static association visibility and layout-order facets.
+
+- A collection with too few displayed scalar columns may produce an under-specified selector action and increase ambiguous-match validation failures.
+ Mitigation: this is consistent with the visible collection shape, and existing validation already rejects ambiguous matches with a clear reason.
+
+- Existing tests or command fixtures may expect parameters for scalar properties that are no longer rendered as collection columns.
+ Mitigation: update fixtures and tests to assert the new column-driven contract.
diff --git a/openspec/changes/limit-selector-parameters-to-collection-columns/proposal.md b/openspec/changes/limit-selector-parameters-to-collection-columns/proposal.md
new file mode 100644
index 00000000000..541b78d654e
--- /dev/null
+++ b/openspec/changes/limit-selector-parameters-to-collection-columns/proposal.md
@@ -0,0 +1,27 @@
+## Why
+
+Synthetic selector actions currently expose eligible scalar child properties as filter parameters even when those properties are not shown as columns in the parented collection.
+This can make the synthetic action prompt diverge from the collection view that the action represents and can expose parameters users would not otherwise use when selecting a row.
+
+## What Changes
+
+- Limit synthetic selector action child filter parameters to properties that appear as columns of the associated parented collection.
+- Order synthetic selector action child filter parameters according to the collection column order.
+- Preserve the mandatory disabled parent parameter before the child filter parameters.
+- Preserve existing exclusions for non-scalar, blob, clob, and technical metadata child properties.
+
+## Capabilities
+
+### New Capabilities
+
+- None.
+
+### Modified Capabilities
+
+- `parented-collection-selector-actions`: synthetic selector action parameter selection and ordering now follow the associated collection columns.
+
+## Impact
+
+- Affects synthetic parented collection selector action parameter discovery in the metamodel.
+- Affects tests covering selector parameter generation and ordering.
+- Does not add new configuration properties, APIs, dependencies, or viewer behavior.
diff --git a/openspec/changes/limit-selector-parameters-to-collection-columns/specs/parented-collection-selector-actions/spec.md b/openspec/changes/limit-selector-parameters-to-collection-columns/specs/parented-collection-selector-actions/spec.md
new file mode 100644
index 00000000000..8227f8e2820
--- /dev/null
+++ b/openspec/changes/limit-selector-parameters-to-collection-columns/specs/parented-collection-selector-actions/spec.md
@@ -0,0 +1,59 @@
+## MODIFIED Requirements
+
+### Requirement: Selector actions expose parent and scalar child parameters
+The synthetic selector action SHALL define one mandatory parent object parameter.
+The parent parameter SHALL be typed as the collection owner type.
+The parent parameter SHALL default to the current action target.
+The parent parameter SHALL be disabled so it cannot be changed by the user.
+The synthetic selector action SHALL define optional parameters only for eligible scalar properties of the collection element type that appear as columns of the associated parented collection.
+The synthetic selector action SHALL order child filter parameters according to the associated parented collection column order.
+The synthetic selector action MUST NOT automatically create selector parameters for child collections or reference properties.
+The synthetic selector action MUST NOT create selector parameters for blob or clob child properties.
+The synthetic selector action MUST NOT create selector parameters for child properties whose ids are `logicalTypeName`, `id`, `version`, `objectIdentifier`, `datanucleusVersionLong`, or `datanucleusVersionTimestamp`.
+
+#### Scenario: Parent parameter is mandatory
+- **GIVEN** an entity type `Lease` has a parented collection `items`
+- **WHEN** the framework synthesizes the selector action for `items`
+- **THEN** the action has a mandatory parameter typed as `Lease`
+
+#### Scenario: Parent parameter defaults to action target
+- **GIVEN** an entity type `Lease` has a parented collection `items`
+- **AND** synthetic parented collection selector action creation is enabled
+- **WHEN** the framework evaluates defaults for the selector action on a `Lease` target
+- **THEN** the parent parameter default is that `Lease` target
+
+#### Scenario: Parent parameter is disabled
+- **GIVEN** an entity type `Lease` has a parented collection `items`
+- **AND** synthetic parented collection selector action creation is enabled
+- **WHEN** the framework evaluates parameter usability for the selector action
+- **THEN** the parent parameter is disabled
+
+#### Scenario: Collection column scalar child properties become optional filters
+- **GIVEN** `Lease.items` renders eligible scalar child properties as collection columns
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action has optional parameters corresponding to those column-backed scalar properties
+
+#### Scenario: Scalar child properties that are not collection columns are excluded from automatic filters
+- **GIVEN** `LeaseItem` has an eligible scalar property that is not rendered as a column of `Lease.items`
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action does not create an automatic parameter for that non-column scalar property
+
+#### Scenario: Child filter parameters follow collection column order
+- **GIVEN** `Lease.items` renders eligible scalar child properties as columns in a configured order
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action lists child filter parameters in the same relative order as the collection columns
+
+#### Scenario: Non-scalar child members are excluded from automatic filters
+- **GIVEN** `LeaseItem` has a child collection and a reference property that appear as collection columns
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action does not create automatic parameters for the child collection or reference property
+
+#### Scenario: Blob and clob child properties are excluded from automatic filters
+- **GIVEN** `LeaseItem` has blob and clob properties that appear as collection columns
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action does not create automatic parameters for the blob or clob properties
+
+#### Scenario: Technical metadata child properties are excluded from automatic filters
+- **GIVEN** `LeaseItem` has scalar properties named `logicalTypeName`, `id`, `version`, `objectIdentifier`, `datanucleusVersionLong`, and `datanucleusVersionTimestamp` that appear as collection columns
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action does not create automatic parameters for `logicalTypeName`, `id`, `version`, `objectIdentifier`, `datanucleusVersionLong`, or `datanucleusVersionTimestamp`
diff --git a/openspec/changes/limit-selector-parameters-to-collection-columns/tasks.md b/openspec/changes/limit-selector-parameters-to-collection-columns/tasks.md
new file mode 100644
index 00000000000..84ed7d1d7ab
--- /dev/null
+++ b/openspec/changes/limit-selector-parameters-to-collection-columns/tasks.md
@@ -0,0 +1,18 @@
+## 1. Selector Parameter Discovery
+
+- [x] 1.1 Update synthetic parented collection selector creation to derive child parameter candidates from the associated collection column model.
+- [x] 1.2 Preserve the mandatory disabled parent parameter as the first selector action parameter.
+- [x] 1.3 Apply the existing scalar, blob, clob, and technical metadata exclusions to the column-derived child parameter candidates.
+- [x] 1.4 Preserve child parameter ordering according to the collection column order.
+
+## 2. Tests
+
+- [x] 2.1 Add or update metamodel tests proving that scalar child properties rendered as collection columns become optional selector filters.
+- [x] 2.2 Add or update tests proving that eligible scalar child properties not rendered as collection columns are omitted.
+- [x] 2.3 Add or update tests proving that child selector filter parameters follow collection column order.
+- [x] 2.4 Keep coverage for parent parameter behavior and existing non-scalar, blob, clob, and technical metadata exclusions.
+
+## 3. Validation
+
+- [x] 3.1 Run the focused metamodel selector action test suite.
+- [x] 3.2 Run `openspec validate limit-selector-parameters-to-collection-columns --strict`.