From 2fc2ee1103ebf16df18fc9c2e05335c4424b31ce Mon Sep 17 00:00:00 2001 From: andi-huber Date: Thu, 23 Apr 2026 19:20:57 +0200 Subject: [PATCH 01/18] CAUSEWAY-3997: [Layout] In-memory Patching of Table Column Order (stubs) Task-Url: https://issues.apache.org/jira/browse/CAUSEWAY-3997 --- .../causeway/applib/CausewayModuleApplib.java | 2 + .../columnorder/Object_patchColumnOrder.java | 87 + ...sViewer_IntegTest.dump_facets.approved.xml | 197 + ...nDomain_IntegTest.dump_facets.approved.xml | 197 + ...sViewer_IntegTest.dump_facets.approved.xml | 197 + ...pDomain_IntegTest.dump_facets.approved.xml | 197 + ...etaModelRegressionTest.verify.approved.xml | 4890 +++++++++++++++++ 7 files changed, 5767 insertions(+) create mode 100644 api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java diff --git a/api/applib/src/main/java/org/apache/causeway/applib/CausewayModuleApplib.java b/api/applib/src/main/java/org/apache/causeway/applib/CausewayModuleApplib.java index 3784ab15ea2..717aa29cd17 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/CausewayModuleApplib.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/CausewayModuleApplib.java @@ -40,6 +40,7 @@ import org.apache.causeway.applib.services.bookmark.BookmarkHolder_object; import org.apache.causeway.applib.services.clock.ClockService; import org.apache.causeway.applib.services.columnorder.Object_downloadColumnOrderTxtFilesAsZip; +import org.apache.causeway.applib.services.columnorder.Object_patchColumnOrder; import org.apache.causeway.applib.services.commanddto.conmap.ContentMappingServiceForCommandDto; import org.apache.causeway.applib.services.commanddto.conmap.ContentMappingServiceForCommandsDto; import org.apache.causeway.applib.services.commanddto.processor.spi.CommandDtoProcessorServiceIdentity; @@ -111,6 +112,7 @@ Dto_downloadXml.class, Dto_downloadXsd.class, Object_downloadColumnOrderTxtFilesAsZip.class, + Object_patchColumnOrder.class, Object_downloadLayout.class, Object_patchLayout.class, Object_downloadMetamodelXml.class, diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java new file mode 100644 index 00000000000..624d2e9758e --- /dev/null +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.causeway.applib.services.columnorder; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.causeway.applib.annotation.Action; +import org.apache.causeway.applib.annotation.ActionLayout; +import org.apache.causeway.applib.annotation.DomainObject; +import org.apache.causeway.applib.annotation.Introspection; +import org.apache.causeway.applib.annotation.MemberSupport; +import org.apache.causeway.applib.annotation.Nature; +import org.apache.causeway.applib.annotation.Parameter; +import org.apache.causeway.applib.annotation.ParameterLayout; +import org.apache.causeway.applib.annotation.PrecedingParamsPolicy; +import org.apache.causeway.applib.annotation.Publishing; +import org.apache.causeway.applib.annotation.RestrictTo; +import org.apache.causeway.applib.annotation.SemanticsOf; +import org.apache.causeway.applib.layout.LayoutConstants; + +import lombok.RequiredArgsConstructor; + +/** + * Allows uploading of column order definition, that overrules the default lookup for such information. + * + * @since 4.0 {@index} + */ +@Action( + domainEvent = Object_patchColumnOrder.ActionDomainEvent.class, + semantics = SemanticsOf.IDEMPOTENT, + commandPublishing = Publishing.DISABLED, + executionPublishing = Publishing.DISABLED, + restrictTo = RestrictTo.PROTOTYPING) +@ActionLayout( + cssClassFa = "solid file-arrow-up", + describedAs = "Uploads table column order, to be stored in memory for this object type. " + + "It overrules the default lookup. " + + "On application restart this information is lost.", + fieldSetId = LayoutConstants.FieldSetId.METADATA, + position = ActionLayout.Position.PANEL_DROPDOWN, + sequence = "700.2.4" +) +//framework provided domain objects and mixins should explicitly specify their introspection policy +@DomainObject(nature=Nature.MIXIN, mixinMethod = "act", introspection = Introspection.ANNOTATION_REQUIRED) +@RequiredArgsConstructor +public class Object_patchColumnOrder { + + public static class ActionDomainEvent + extends org.apache.causeway.applib.CausewayModuleApplib.ActionDomainEvent {} + + @Inject ColumnOrderTxtFileService columnOrderTxtFileService; + + private final Object mixee; + + @MemberSupport public Object act( + final String memberId, + @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.PRESERVE_CHANGES) + @ParameterLayout(multiLine = 20) + final String columnDefinition) { + // TODO flesh out + return mixee; + } + + @MemberSupport public List choicesMemberId() { + // TODO flesh out + return List.of(); + } + +} diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml index 1b5947078a3..6c9be633cb4 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml @@ -1026,6 +1026,203 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml index d75e9bd529d..cfec7a0c966 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml @@ -1019,6 +1019,203 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml index 56829cdfd55..ae833fc9e45 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml @@ -1015,6 +1015,203 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml index 00fdfd88e71..3518b60e7ad 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml @@ -1008,6 +1008,203 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index d7c43900f51..8ed73cd5d0f 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -883,6 +883,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -1923,6 +2086,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -3208,6 +3534,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -4320,6 +4809,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -5420,6 +6072,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -6833,6 +7648,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -8384,6 +9362,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -10127,6 +11268,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -11353,6 +12657,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -12563,6 +14030,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -13795,6 +15425,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -15052,6 +16845,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -16372,6 +18328,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -17852,6 +19971,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -19261,6 +21543,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -20381,6 +22826,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -21501,6 +24109,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -25437,6 +28208,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -26688,6 +29622,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -28171,6 +31268,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -29658,6 +32918,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -34867,6 +38290,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -37608,6 +41194,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -38723,6 +42472,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -39927,6 +43839,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -41469,6 +45544,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -42933,6 +47171,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -44397,6 +48798,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -45907,6 +50471,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + @@ -47183,6 +51910,169 @@ org.apache.causeway.applib.value.LocalResourcePath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang.String + + + From 1f6525f300f3a8b965c3e492e452158507b54a94 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Tue, 28 Apr 2026 09:53:21 +0200 Subject: [PATCH 02/18] CAUSEWAY-3997: flesh out collection-id choices --- .../ColumnOrderTxtFileService.java | 20 +++++---- ...ject_downloadColumnOrderTxtFilesAsZip.java | 4 +- .../columnorder/Object_patchColumnOrder.java | 21 +++++++--- .../ColumnOrderTxtFileServiceDefault.java | 42 +++++++++++-------- .../spec/impl/_MembersAsColumns.java | 14 +++---- 5 files changed, 61 insertions(+), 40 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java index fb8a647dc87..1044fbdf8ce 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java @@ -18,34 +18,40 @@ */ package org.apache.causeway.applib.services.columnorder; +import java.util.List; + +import org.apache.causeway.applib.annotation.Collection; import org.apache.causeway.applib.services.tablecol.TableColumnOrderService; /** * This is a utility service to support the usage of {@link TableColumnOrderService}, providing the ability to obtain * a zip of each of the Xxx.columnOrder.txt files for the specified domain object. * - *

- * The zip contains: + *

The zip contains: *

    *
  • DomainClass.columnOrder.txt
  • - as used for standalone collections of DomainClass itself *
  • DomainClass#collection1.columnOrder.txt
  • - for DomainClass' collection with id collection1. *
  • ...
  • *
  • DomainClass#collectionN.columnOrder.txt
  • - for DomainClass' collection with id collectionN. *
- *

* - *

- * These should be unzipped and copied in the domain class' package, and then their contents updated to specify the + *

These should be un-zipped and copied in the domain class' package, and then their contents updated to specify the * order in which the respective object's properties will be shown in the standalone or parented collections. - *

* * @see Object_downloadColumnOrderTxtFilesAsZip + * @see Object_patchColumnOrder * @see TableColumnOrderService * * @since 2.0 {@index} */ public interface ColumnOrderTxtFileService { - byte[] toZip(final Object domainObject); + byte[] toZip(Object domainObject); + + /** + * List of memberIds for given domainObject, that represent a {@link Collection}, including mixed-in ones. + * @since 4.0 + */ + List collectionIds(Object domainObject); } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_downloadColumnOrderTxtFilesAsZip.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_downloadColumnOrderTxtFilesAsZip.java index ba974440f3d..24e615b39dc 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_downloadColumnOrderTxtFilesAsZip.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_downloadColumnOrderTxtFilesAsZip.java @@ -74,6 +74,8 @@ @RequiredArgsConstructor public class Object_downloadColumnOrderTxtFilesAsZip { + @Inject ColumnOrderTxtFileService columnOrderTxtFileService; + private final Object domainObject; // mixee public static class ActionDomainEvent @@ -88,6 +90,4 @@ public static class ActionDomainEvent return String.format("%s.columnOrder.zip", domainObject.getClass().getSimpleName()); } - @Inject ColumnOrderTxtFileService columnOrderTxtFileService; - } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java index 624d2e9758e..80df58d6f28 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -28,6 +28,7 @@ import org.apache.causeway.applib.annotation.Introspection; import org.apache.causeway.applib.annotation.MemberSupport; import org.apache.causeway.applib.annotation.Nature; +import org.apache.causeway.applib.annotation.Optionality; import org.apache.causeway.applib.annotation.Parameter; import org.apache.causeway.applib.annotation.ParameterLayout; import org.apache.causeway.applib.annotation.PrecedingParamsPolicy; @@ -62,7 +63,7 @@ @DomainObject(nature=Nature.MIXIN, mixinMethod = "act", introspection = Introspection.ANNOTATION_REQUIRED) @RequiredArgsConstructor public class Object_patchColumnOrder { - + public static class ActionDomainEvent extends org.apache.causeway.applib.CausewayModuleApplib.ActionDomainEvent {} @@ -71,17 +72,25 @@ public static class ActionDomainEvent private final Object mixee; @MemberSupport public Object act( - final String memberId, + @Parameter(optionality = Optionality.OPTIONAL) + @ParameterLayout(describedAs = "The Collection, for which the patch is to be applied (in-memory). " + + "If 'none', patches all standalone tables, " + + "where this domain object type is the element type.") + final String collectionId, @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.PRESERVE_CHANGES) @ParameterLayout(multiLine = 20) final String columnDefinition) { // TODO flesh out return mixee; } - - @MemberSupport public List choicesMemberId() { - // TODO flesh out - return List.of(); + + @MemberSupport public List choicesCollectionId() { + return columnOrderTxtFileService.collectionIds(mixee); + } + + @MemberSupport public String defaultColumnDefinition() { + // TODO flesh out - fill from current, or should we bring in listing-support from causeway-stuff? + return ""; } } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java index a428dc4ef4f..5984b59805e 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java @@ -18,8 +18,9 @@ */ package org.apache.causeway.core.metamodel.services.columnorder; +import java.util.List; + import jakarta.annotation.Priority; -import jakarta.inject.Inject; import jakarta.inject.Named; import org.springframework.beans.factory.annotation.Qualifier; @@ -38,8 +39,6 @@ import org.apache.causeway.core.metamodel.spec.feature.OneToManyAssociation; import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; -import lombok.RequiredArgsConstructor; - /** * Default implementation of {@link ColumnOrderTxtFileService}. * @@ -49,42 +48,48 @@ @Named(CausewayModuleCoreMetamodel.NAMESPACE + ".ColumnOrderTxtFileServiceDefault") @Priority(PriorityPrecedence.LATE) @Qualifier("Default") -@RequiredArgsConstructor(onConstructor_ = {@Inject}) -public class ColumnOrderTxtFileServiceDefault implements ColumnOrderTxtFileService { - - final SpecificationLoader specificationLoader; +public record ColumnOrderTxtFileServiceDefault( + SpecificationLoader specificationLoader) implements ColumnOrderTxtFileService { @Override @Programmatic public byte[] toZip(final Object domainObject) { + var objSpec = specificationLoader.loadSpecification(domainObject.getClass()); var zipBuilder = ZipUtils.zipEntryBuilder(); - addStandaloneEntry(domainObject, zipBuilder); - addCollectionEntries(domainObject, zipBuilder); + addStandaloneEntry(objSpec, zipBuilder); + addCollectionEntries(objSpec, zipBuilder); return zipBuilder.toBytes(); } - // HELPERS + @Override + @Programmatic + public List collectionIds(final Object domainObject) { + var objSpec = specificationLoader.loadSpecification(domainObject.getClass()); + return objSpec.streamCollections(MixedIn.INCLUDED) + .map(ObjectFeature::getId) + .toList(); + } + + // -- HELPER - private void addStandaloneEntry(final Object domainObject, final ZipUtils.EntryBuilder zipBuilder) { - var parentSpec = specificationLoader.loadSpecification(domainObject.getClass()); + private void addStandaloneEntry(final ObjectSpecification objSpec, final ZipUtils.EntryBuilder zipBuilder) { var buf = new StringBuilder(); - parentSpec.streamAssociations(MixedIn.INCLUDED) + objSpec.streamAssociations(MixedIn.INCLUDED) .map(ObjectFeature::getId) .forEach(assocId -> buf.append(assocId).append("\n")); var fileContents = buf.toString(); - var fileName = String.format("%s.columnOrder.txt", parentSpec.getShortIdentifier()); + var fileName = String.format("%s.columnOrder.txt", objSpec.getShortIdentifier()); zipBuilder.addAsUtf8(fileName, fileContents); } - private void addCollectionEntries(final Object domainObject, final ZipUtils.EntryBuilder zipBuilder) { - var parentSpec = specificationLoader.loadSpecification(domainObject.getClass()); - parentSpec.streamCollections(MixedIn.INCLUDED) - .forEach(collection -> addCollection(collection, parentSpec, zipBuilder)); + private void addCollectionEntries(final ObjectSpecification objSpec, final ZipUtils.EntryBuilder zipBuilder) { + objSpec.streamCollections(MixedIn.INCLUDED) + .forEach(collection -> addCollection(collection, objSpec, zipBuilder)); } private void addCollection( @@ -94,6 +99,7 @@ private void addCollection( var buf = new StringBuilder(); + //TODO this does not account for any SPI from _MembersAsColumns - problem? collection.getElementType() .streamAssociations(MixedIn.INCLUDED) .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(Where.PARENTED_TABLES)) diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java index a4995f6acfd..b425e8ac036 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java @@ -18,21 +18,23 @@ */ package org.apache.causeway.core.metamodel.spec.impl; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.NonNull; + import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.Where; import org.apache.causeway.applib.layout.component.PropertyLayoutData; import org.apache.causeway.applib.services.tablecol.TableColumnOrderService; import org.apache.causeway.applib.services.tablecol.TableColumnVisibilityService; import org.apache.causeway.commons.internal.base._NullSafe; -import org.apache.causeway.commons.internal.collections._Lists; -import org.apache.causeway.commons.internal.collections._Maps; import org.apache.causeway.core.metamodel.context.HasMetaModelContext; import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.core.metamodel.facets.object.grid.GridFacet; @@ -47,7 +49,6 @@ import static org.apache.causeway.applib.annotation.Where.STANDALONE_TABLES; import lombok.Getter; -import org.jspecify.annotations.NonNull; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -79,7 +80,7 @@ public final Stream streamAssociationsForColumnRendering( var parentSpecIfAny = parentObject.objSpec(); - var assocById = _Maps.newLinkedHashMap(); + var assocById = new LinkedHashMap(); elementType.streamAssociations(MixedIn.INCLUDED) .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(WhereContexts.collectionVariant(memberIdentifier))) @@ -87,7 +88,7 @@ public final Stream streamAssociationsForColumnRendering( .filter(assoc->filterColumnsUsingSpi(assoc, elementClass)) // optional SPI to filter columns; .forEach(assoc->assocById.put(assoc.getId(), assoc)); - var assocIdsInOrder = _Lists.newArrayList(assocById.keySet()); + var assocIdsInOrder = new ArrayList(assocById.keySet()); // sort by order of occurrence within associated layout, if any propertyIdComparator(elementType) @@ -156,9 +157,8 @@ private void sortColumnsUsingSpi( final Class elementType) { var tableColumnOrderServices = getServiceRegistry().select(TableColumnOrderService.class); - if(tableColumnOrderServices.isEmpty()) { + if(tableColumnOrderServices.isEmpty()) return; - } var whereContext = whereContextFor(memberIdentifier); From e91039836033e136650fb1a0ff31458e3ead3808 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Wed, 29 Apr 2026 06:03:11 +0200 Subject: [PATCH 03/18] CAUSEWAY-3997: test approvals --- ...sViewer_IntegTest.dump_facets.approved.xml | 35 +- ...nDomain_IntegTest.dump_facets.approved.xml | 35 +- ...sViewer_IntegTest.dump_facets.approved.xml | 35 +- ...pDomain_IntegTest.dump_facets.approved.xml | 35 +- ...etaModelRegressionTest.verify.approved.xml | 990 ++++++++++++++---- 5 files changed, 876 insertions(+), 254 deletions(-) diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml index 6c9be633cb4..75917e6faf1 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml @@ -1136,25 +1136,32 @@
java.lang.Object - + + + + + + + + - + - + - - + + - - + + @@ -1170,12 +1177,12 @@ - - + + - - + + @@ -1213,6 +1220,12 @@ + + + + + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml index cfec7a0c966..3cc73d588aa 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml @@ -1129,25 +1129,32 @@ java.lang.Object - + + + + + + + + - + - + - - + + - - + + @@ -1163,12 +1170,12 @@ - - + + - - + + @@ -1206,6 +1213,12 @@ + + + + + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml index ae833fc9e45..3c2628e73d6 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml @@ -1125,25 +1125,32 @@ java.lang.Object - + + + + + + + + - + - + - - + + - - + + @@ -1159,12 +1166,12 @@ - - + + - - + + @@ -1202,6 +1209,12 @@ + + + + + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml index 3518b60e7ad..8fd339014a9 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml @@ -1118,25 +1118,32 @@ java.lang.Object - + + + + + + + + - + - + - - + + - - + + @@ -1152,12 +1159,12 @@ - - + + - - + + @@ -1195,6 +1202,12 @@ + + + + + + diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index 8ed73cd5d0f..c3c5fcec45c 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -988,29 +988,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -1036,6 +1049,12 @@ + + + + + + @@ -2191,29 +2210,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -2239,6 +2271,12 @@ + + + + + + @@ -3639,29 +3677,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -3687,6 +3738,12 @@ + + + + + + @@ -4914,29 +4971,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -4962,6 +5032,12 @@ + + + + + + @@ -6177,29 +6253,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -6225,6 +6314,12 @@ + + + + + + @@ -7753,29 +7848,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -7801,6 +7909,12 @@ + + + + + + @@ -9467,29 +9581,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -9515,6 +9642,12 @@ + + + + + + @@ -11373,29 +11506,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -11421,6 +11567,12 @@ + + + + + + @@ -12762,29 +12914,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -12810,6 +12975,12 @@ + + + + + + @@ -14135,29 +14306,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -14183,6 +14367,12 @@ + + + + + + @@ -15530,29 +15720,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -15578,6 +15781,12 @@ + + + + + + @@ -16950,29 +17159,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -16998,6 +17220,12 @@ + + + + + + @@ -18433,29 +18661,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -18481,6 +18722,12 @@ + + + + + + @@ -20076,29 +20323,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -20124,6 +20384,12 @@ + + + + + + @@ -21648,29 +21914,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -21696,6 +21975,12 @@ + + + + + + @@ -22931,29 +23216,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -22979,6 +23277,12 @@ + + + + + + @@ -24214,29 +24518,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -24262,6 +24579,12 @@ + + + + + + @@ -28313,29 +28636,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -28361,6 +28697,12 @@ + + + + + + @@ -29727,29 +30069,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -29775,6 +30130,12 @@ + + + + + + @@ -31373,29 +31734,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -31421,6 +31795,12 @@ + + + + + + @@ -33023,29 +33403,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -33071,6 +33464,12 @@ + + + + + + @@ -38395,29 +38794,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -38443,6 +38855,12 @@ + + + + + + @@ -41299,29 +41717,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -41347,6 +41778,12 @@ + + + + + + @@ -42577,29 +43014,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -42625,6 +43075,12 @@ + + + + + + @@ -43944,29 +44400,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -43992,6 +44461,12 @@ + + + + + + @@ -45649,29 +46124,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -45697,6 +46185,12 @@ + + + + + + @@ -47276,29 +47770,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -47324,6 +47831,12 @@ + + + + + + @@ -48903,29 +49416,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -48951,6 +49477,12 @@ + + + + + + @@ -50576,29 +51108,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -50624,6 +51169,12 @@ + + + + + + @@ -52015,29 +52566,42 @@ java.lang.Object - + + + + + + + + - + - + + + + + + + - - + + - - + + @@ -52063,6 +52627,12 @@ + + + + + + From 8658941fa9c0774ecdc52e244c77d1ec2034e035 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Wed, 29 Apr 2026 06:47:48 +0200 Subject: [PATCH 04/18] CAUSEWAY-3997: brings in Listing from causewaystuff --- .../apache/causeway/applib/value/Listing.java | 310 ++++++++++++++++++ .../causeway/applib/value/ListingTest.java | 157 +++++++++ .../ColumnOrderTxtFileServiceDefault.java | 4 +- 3 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 api/applib/src/main/java/org/apache/causeway/applib/value/Listing.java create mode 100644 api/applib/src/test/java/org/apache/causeway/applib/value/ListingTest.java diff --git a/api/applib/src/main/java/org/apache/causeway/applib/value/Listing.java b/api/applib/src/main/java/org/apache/causeway/applib/value/Listing.java new file mode 100644 index 00000000000..8ba539ed56d --- /dev/null +++ b/api/applib/src/main/java/org/apache/causeway/applib/value/Listing.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.causeway.applib.value; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import org.apache.causeway.applib.annotation.Value; +import org.apache.causeway.commons.collections.Can; +import org.apache.causeway.commons.functional.Try; +import org.apache.causeway.commons.internal.base._NullSafe; +import org.apache.causeway.commons.io.TextUtils; + +/** + * Represents a list of lines (of text), + * where each {@link Line} has semantics, such as being able to be commented out. + * + *

Each non-comment line can be mapped to a Java class of type {@code T}. + * @param type each non-comment line can be mapped to + * @since 4.0 + */ +@Value +public record Listing( + @NonNull ListingHandler handler, + @NonNull Can lines) { + + public record ListingHandler ( + @NonNull Class objectType, + @NonNull Function stringifier, + @NonNull Function destringifier, + @NonNull Function keyExtractor) { + + // -- LISTING FACTORIES + + public Listing emptyListing() { + return createListing(Can.empty()); + } + + public Listing createListing(@Nullable final Iterable enabledElements) { + return createListing(_NullSafe.stream(enabledElements)); + } + public Listing createListing(@Nullable final Stream enabledElementStream) { + if(enabledElementStream==null) return emptyListing(); + return new Listing<>(this, enabledElementStream + .map(t->new LineEnabled<>(t, stringifier().apply(t))) + .collect(Can.toCan())); + } + + public Listing parseListing(@Nullable final String wholeText) { + if(wholeText==null) return emptyListing(); + return parseListing(TextUtils.readLines(wholeText)); + } + public Listing parseListing(@Nullable final Can textLines) { + if(textLines==null) return emptyListing(); + return new Listing<>(this, textLines.map(this::parseLine)); + } + + // -- PARSING + + /** + * Parses the whole line as is, including comments. + *

+ * If a line is not a comment, but can also not be mapped to {@code T}, + * then it is commented out. + */ + public Line parseLine(final String wholeLine) { + final var line = wholeLine.trim(); + if(line.isBlank()) + return new LineComment(line); + if(line.startsWith("#")) { + var trimmed = line; + while(trimmed.startsWith("#")) { + trimmed = trimmed.substring(1).trim(); + } + T object = asT(trimmed).getValue().orElse(null); + return object==null + ? new LineComment(wholeLine) + : (line.startsWith("REMOVED ")) + ? new LineRemoved<>(object, trimmed.substring(8)) + : new LineDisabled<>(object, trimmed); + } + Try asT = asT(line); + return asT + .mapSuccessWhenPresent(object->(Line)new LineEnabled(object, line)) + .mapEmptyToFailure() + .mapFailureToSuccess(e->new LineComment(String.format("#ERROR cannot parse ‹%s› as %s (%s)", + line, objectType().getSimpleName(), e))) + .valueAsNonNullElseFail(); + } + + // -- HELPER + + private Try asT(final String stringified) { + return Try.call(()->destringifier().apply(stringified)); + } + + } + + public sealed interface Line + permits MappedLine, LineComment{ + String format(); + } + public sealed interface MappedLine + extends Line + permits LineEnabled, LineDisabled, LineRemoved { + T object(); + } + + /** + * Can be mapped to {@code T}. Not commented out. + */ + public record LineEnabled( + @NonNull T object, + @NonNull String objectStringified) + implements MappedLine { + @Override public String format() { + return objectStringified; + } + public LineDisabled toDisabled() { + return new LineDisabled<>(object, objectStringified); + } + } + /** + * Can be mapped to {@code T}, but commented out. + */ + public record LineDisabled( + @NonNull T object, + @NonNull String objectStringified) + implements MappedLine { + @Override public String format() { + return "#" + objectStringified; + } + public LineEnabled toEnabled() { + return new LineEnabled<>(object, objectStringified); + } + public LineRemoved toRemoved() { + return new LineRemoved<>(object, objectStringified); + } + } + /** + * Can be mapped to {@code T}, but was removed, hence commented out. + */ + public record LineRemoved( + @NonNull T object, + @NonNull String objectStringified) + implements MappedLine { + @Override public String format() { + return "#REMOVED " + objectStringified; + } + public LineDisabled toDisabled() { + return new LineDisabled<>(object, objectStringified); + } + } + /** + * Blank line or arbitrary comment, cannot be mapped to {@code T}. + */ + public record LineComment( + @NonNull String comment) + implements Line { + @Override public String format() { + return comment; + } + } + + // -- STREAMS + + public Stream> streamEnabledLines() { + return lines().stream().filter(LineEnabled.class::isInstance).map(LineEnabled.class::cast); + } + public Stream> streamDisabledLines() { + return lines().stream().filter(LineDisabled.class::isInstance).map(LineDisabled.class::cast); + } + public Stream streamEnabled() { + return streamEnabledLines().map(LineEnabled::object); + } + public Stream streamDisabled() { + return streamDisabledLines().map(LineDisabled::object); + } + public Stream streamComments() { + return lines().stream().filter(LineComment.class::isInstance).map(LineComment.class::cast); + } + + // -- FORMAT + + @Override + public final String toString() { + return lines().map(Line::format).join("\n"); + } + + // -- MERGE + + public enum MergePolicy { + ADD_NEW_AS_ENABLED, + ADD_NEW_AS_DISABLED + } + + /** + * Say this listing was edited by a human, + * but needs to be synchronized with a newer version originating from some system process, + * then we'd like to merge in this new information, + * without loosing any information that is already present in this listing such as: + *

    + *
  • comments
  • + *
  • line ordering
  • + *
+ * + *

We do this by adding a comment line {@code #MERGED} followed by any lines that are new. + * + *

Any lines already existing are kept as they are, + * unless the {@code newerVersion} no longer contains the referenced object of type {@code T}, + * in which case, the {@link Line} will be commented out (if not already) with a marker + * {@code #REMOVED}. + * + *

This requires the merge algorithm to evaluate whether 2 referenced objects are equal, + * which it does by checking object keys as given by {@link ListingHandler#keyExtractor} + * for equality. We could have done the same by directly checking referenced objects for equality, + * but - worst case - that would involve entire objects graphs to be assembled, + * while for our use case its convenient to work with simple object facades. + */ + public Listing merge(@NonNull final MergePolicy policy, @Nullable final Listing newerVersion) { + if(newerVersion==null) return this; + final Map> incomingByKey = newerVersion.streamEnabledLines() + .collect(Collectors.toMap( + line->handler().keyExtractor().apply(line.object()), + UnaryOperator.identity(), + (a, b)->a, + LinkedHashMap::new)); + if(incomingByKey.isEmpty()) return this; + + var mergedLines = new ArrayList(); + + // if in but NOT in -> add to merged (honor LineMergePolicy) + // if NOT in but in -> mark #REMOVED if not already + // if in both all AND b -> add to merged (keep enabled-state as defined by b) + lines().stream() + .forEach((final Line line)->{ + @SuppressWarnings("unchecked") + var key = (line instanceof MappedLine mappedLine) + ? handler().keyExtractor().apply((T) mappedLine.object()) + : null; + final boolean incomingContainsKey = key!=null + ? incomingByKey.remove(key)!=null + : false; + + if(line instanceof LineComment comment) { + mergedLines.add(comment); // identity operation + } else if(line instanceof LineEnabled lineEnabled) { + if(incomingContainsKey) { + mergedLines.add(lineEnabled); // identity operation + } else { + mergedLines.add(lineEnabled.toDisabled().toRemoved()); // mark REMOVED + } + incomingByKey.remove(key); + } else if(line instanceof LineDisabled lineDisabled) { + if(incomingContainsKey) { + mergedLines.add(lineDisabled); // identity operation + } else { + mergedLines.add(lineDisabled.toRemoved()); // mark REMOVED + } + } else if(line instanceof LineRemoved lineRemoved) { + if(incomingContainsKey) { + mergedLines.add(lineRemoved); // identity operation + } else { + mergedLines.add(lineRemoved.toDisabled()); // unmark REMOVED + } + } + }); + + // process the remaining incoming lines + if(!incomingByKey.isEmpty()) { + if(!mergedLines.isEmpty()) { + mergedLines.add(new LineComment("#MERGED")); // skip if we are filling an empty listing + } + if(MergePolicy.ADD_NEW_AS_ENABLED==policy) { + incomingByKey.values() + .forEach(mergedLines::add); + } else { + incomingByKey.values() + .forEach(lineEnabled->mergedLines.add(lineEnabled.toDisabled())); + } + } + + return new Listing<>(handler, Can.ofCollection(mergedLines)); + } + +} diff --git a/api/applib/src/test/java/org/apache/causeway/applib/value/ListingTest.java b/api/applib/src/test/java/org/apache/causeway/applib/value/ListingTest.java new file mode 100644 index 00000000000..f07b2d064a6 --- /dev/null +++ b/api/applib/src/test/java/org/apache/causeway/applib/value/ListingTest.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.causeway.applib.value; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.springframework.util.StringUtils; + +import org.apache.causeway.applib.value.Listing.ListingHandler; +import org.apache.causeway.applib.value.Listing.MergePolicy; +import org.apache.causeway.commons.collections.Can; +import org.apache.causeway.commons.io.TextUtils; + +class ListingTest { + + record Customer( + String id, + String name) { + Customer(final String id, final String name) { + assertTrue(StringUtils.hasLength(id)); + assertTrue(StringUtils.hasLength(name)); + this.id = id; + this.name = name; + } + String stringify() { + return String.format("[%s] %s", id, name); + } + static Customer destringify(final String input) { + int i0 = input.indexOf('['); + int i1 = input.indexOf(']'); + assertTrue(i0>-1); + assertTrue(i1>i0); + var cutter = TextUtils.cutter(input); + var id = cutter.keepAfter("[") + .keepBefore("]") + .getValue(); + var name = cutter.keepAfter("]") + .getValue() + .trim(); + return new Customer(id, name); + } + } + + private final ListingHandler listingHandler = + new ListingHandler<>(Customer.class, Customer::stringify, Customer::destringify, Customer::id); + private final Listing listing = + sampleListing(listingHandler); + + @Test + void parsing() { + var enabledCustomers = listing.streamEnabled().collect(Can.toCan()); + assertElementsMatch(Can.of( + new Customer("a", "Jeff"), + new Customer("b", "Jane")), + enabledCustomers); + + var disabledCustomers = listing.streamDisabled().collect(Can.toCan()); + assertElementsMatch(Can.of( + new Customer("c", "Henry")), + disabledCustomers); + } + + @Test + void writing() { + String expectedOutputAfterRoundtrip = """ + # this is a regular comment + [a] Jeff + #ERROR cannot parse ‹this is an invalid line› as Customer (org.opentest4j.AssertionFailedError: expected: but was: ) + #ERROR cannot parse ‹also an # invalid line› as Customer (org.opentest4j.AssertionFailedError: expected: but was: ) + #[c] Henry + # the follwing is a blank line + + [b] Jane + """; + //debug + //System.err.printf("%s%n", listing); + assertTextLinesMatch( + expectedOutputAfterRoundtrip, + listing.toString()); + } + + @Test + void merging() { + String upd = """ + # this is a listing of updates we want to apply + [a] Jeff + [c] Henry + [d] Martha + """; + var updateListing = listingHandler.parseListing(upd); + var merged = listing.merge(MergePolicy.ADD_NEW_AS_DISABLED, updateListing); + String expectedOutputAfterMerge = """ + # this is a regular comment + [a] Jeff + #ERROR cannot parse ‹this is an invalid line› as Customer (org.opentest4j.AssertionFailedError: expected: but was: ) + #ERROR cannot parse ‹also an # invalid line› as Customer (org.opentest4j.AssertionFailedError: expected: but was: ) + #[c] Henry + # the follwing is a blank line + + #REMOVED [b] Jane + + #MERGED + #[d] Martha"""; + //debug + //System.err.printf("%s%n", merged); + assertTextLinesMatch( + expectedOutputAfterMerge, + merged.toString()); + } + + // -- HELPER + + private Listing sampleListing(final ListingHandler listingHandler) { + String input = """ + # this is a regular comment + [a] Jeff + this is an invalid line + also an # invalid line + # ## [c] Henry + # the follwing is a blank line + + [b] Jane + """; + return listingHandler.parseListing(input); + } + + private static void assertTextLinesMatch(final String a, final String b) { + assertLinesMatch( + TextUtils.readLines(a).toList(), + TextUtils.readLines(b).toList()); + } + + private static void assertElementsMatch(final Can a, final Can b) { + assertTextLinesMatch( + a.join("\n"), + b.join("\n")); + } +} diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java index 5984b59805e..6708e308db1 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java @@ -82,7 +82,7 @@ private void addStandaloneEntry(final ObjectSpecification objSpec, final ZipUtil .forEach(assocId -> buf.append(assocId).append("\n")); var fileContents = buf.toString(); - var fileName = String.format("%s.columnOrder.txt", objSpec.getShortIdentifier()); + var fileName = "%s.columnOrder.txt".formatted(objSpec.getShortIdentifier()); zipBuilder.addAsUtf8(fileName, fileContents); } @@ -107,7 +107,7 @@ private void addCollection( .map(ObjectFeature::getId) .forEach(assocId -> buf.append(assocId).append("\n")); - var fileName = String.format("%s#%s.columnOrder.txt", parentSpec.getShortIdentifier(), collection.getId()); + var fileName = "%s#%s.columnOrder.txt".formatted(parentSpec.getShortIdentifier(), collection.getId()); var fileContents = buf.toString(); zipBuilder.addAsUtf8(fileName, fileContents); From 8cefc8d056f35cbf7ff2f76ba4e4accb74051fed Mon Sep 17 00:00:00 2001 From: andi-huber Date: Wed, 29 Apr 2026 07:50:38 +0200 Subject: [PATCH 05/18] CAUSEWAY-3997: new methods for MetaModelService to support built-in Mixins that only have access to Applib --- .../ColumnOrderTxtFileService.java | 10 -- .../columnorder/Object_patchColumnOrder.java | 14 +- .../services/metamodel/MetaModelService.java | 41 ++++-- .../applib/{value => util}/Listing.java | 11 +- .../applib/{value => util}/ListingTest.java | 6 +- .../ColumnOrderTxtFileServiceDefault.java | 11 -- .../metamodel/MetaModelServiceDefault.java | 88 ++++++++----- ...sViewer_IntegTest.dump_facets.approved.xml | 4 +- ...nDomain_IntegTest.dump_facets.approved.xml | 4 +- ...sViewer_IntegTest.dump_facets.approved.xml | 4 +- ...pDomain_IntegTest.dump_facets.approved.xml | 4 +- ...etaModelRegressionTest.verify.approved.xml | 120 +++++++++--------- 12 files changed, 171 insertions(+), 146 deletions(-) rename api/applib/src/main/java/org/apache/causeway/applib/{value => util}/Listing.java (97%) rename api/applib/src/test/java/org/apache/causeway/applib/{value => util}/ListingTest.java (97%) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java index 1044fbdf8ce..cd38cdbaa84 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/ColumnOrderTxtFileService.java @@ -18,9 +18,6 @@ */ package org.apache.causeway.applib.services.columnorder; -import java.util.List; - -import org.apache.causeway.applib.annotation.Collection; import org.apache.causeway.applib.services.tablecol.TableColumnOrderService; /** @@ -47,11 +44,4 @@ public interface ColumnOrderTxtFileService { byte[] toZip(Object domainObject); - - /** - * List of memberIds for given domainObject, that represent a {@link Collection}, including mixed-in ones. - * @since 4.0 - */ - List collectionIds(Object domainObject); - } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java index 80df58d6f28..e8273008183 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -36,6 +36,8 @@ import org.apache.causeway.applib.annotation.RestrictTo; import org.apache.causeway.applib.annotation.SemanticsOf; import org.apache.causeway.applib.layout.LayoutConstants; +import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; +import org.apache.causeway.applib.services.metamodel.MetaModelService; import lombok.RequiredArgsConstructor; @@ -67,7 +69,7 @@ public class Object_patchColumnOrder { public static class ActionDomainEvent extends org.apache.causeway.applib.CausewayModuleApplib.ActionDomainEvent {} - @Inject ColumnOrderTxtFileService columnOrderTxtFileService; + @Inject MetaModelService metaModelService; private final Object mixee; @@ -76,7 +78,7 @@ public static class ActionDomainEvent @ParameterLayout(describedAs = "The Collection, for which the patch is to be applied (in-memory). " + "If 'none', patches all standalone tables, " + "where this domain object type is the element type.") - final String collectionId, + final ApplicationFeatureId collectionId, @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.PRESERVE_CHANGES) @ParameterLayout(multiLine = 20) final String columnDefinition) { @@ -84,12 +86,14 @@ public static class ActionDomainEvent return mixee; } - @MemberSupport public List choicesCollectionId() { - return columnOrderTxtFileService.collectionIds(mixee); + @MemberSupport public List choicesCollectionId() { + return metaModelService.streamCollections(mixee.getClass()) + .map(ApplicationFeatureId::fromIdentifier) + .toList(); } @MemberSupport public String defaultColumnDefinition() { - // TODO flesh out - fill from current, or should we bring in listing-support from causeway-stuff? + // TODO flesh out - using Listing return ""; } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java index 7fd76afec5a..a274ba0b448 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java @@ -20,11 +20,14 @@ import java.util.Optional; import java.util.function.BiPredicate; +import java.util.stream.Stream; import jakarta.inject.Named; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.Action; import org.apache.causeway.applib.annotation.DomainObject; import org.apache.causeway.applib.annotation.DomainService; @@ -36,8 +39,6 @@ import org.apache.causeway.commons.collections.Can; import org.apache.causeway.schema.metamodel.v2.MetamodelDto; -import org.jspecify.annotations.NonNull; - /** * This service provides a formal API into the framework's metamodel. * @@ -56,7 +57,7 @@ public interface MetaModelService { * such non-abstract class registered. * (interfaces and abstract types are never added to the lookup table). */ - Optional lookupLogicalTypeByName(final String logicalTypeName); + Optional lookupLogicalTypeByName(String logicalTypeName); /** * Assuming that the {@link LogicalType} passed in actually represents a domain type, then @@ -69,7 +70,7 @@ public interface MetaModelService { * * @param logicalType */ - Can logicalTypeAndAliasesFor(final LogicalType logicalType); + Can logicalTypeAndAliasesFor(LogicalType logicalType); /** * Returns the {@link LogicalType} of a domain class' object type, corresponding to {@link Named#value()}, @@ -80,20 +81,20 @@ public interface MetaModelService { * If there is no such domain type, then an empty {@link Can} will be returned. *

*/ - Can logicalTypeAndAliasesFor(final String logicalTypeName); + Can logicalTypeAndAliasesFor(String logicalTypeName); /** * Provides a lookup by class of a domain class' object type,corresponding to * {@link Named#value()} or {@link DomainService#aliased()} or * {@link DomainObject#aliased()}. */ - Optional lookupLogicalTypeByClass(final Class domainType); + Optional lookupLogicalTypeByClass(Class domainType); /** * Invalidates and rebuilds the internal metadata for the specified domain * type. */ - void rebuild(final Class domainType); + void rebuild(Class domainType); /** * Returns a list of representations of each of member of each domain class. @@ -137,8 +138,7 @@ public interface MetaModelService { * {@link org.apache.causeway.applib.services.conmap.ContentMappingService}. *

*/ - CommandDtoProcessor commandDtoProcessorFor( - String logicalMemberIdentifier); + CommandDtoProcessor commandDtoProcessorFor(String logicalMemberIdentifier); /** * How {@link MetaModelService#sortOf(Class, Mode)} should act if an object @@ -166,13 +166,32 @@ enum Mode { * * @param config - restricts/filters to a subsets of the metamodel. */ - MetamodelDto exportMetaModel(final Config config); + MetamodelDto exportMetaModel(Config config); /** * Can be used to create object relation diagrams (e.g. Plantuml). * * @param filter by {@link BeanSort} and {@link LogicalType} what to include in the resulting graph */ - ObjectGraph exportObjectGraph(final @NonNull BiPredicate filter); + ObjectGraph exportObjectGraph(@NonNull BiPredicate filter); + + /** + * Stream of {@link Identifier} representing the Actions of given domainType, including mixed-in ones, + * but excluding Actions, that are only available for PROTOTYPING. + * @since 4.0 + */ + Stream streamActions(@Nullable Class domainType); + + /** + * Stream of {@link Identifier} representing the Properties of given domainType, including mixed-in ones. + * @since 4.0 + */ + Stream streamProperties(@Nullable Class domainType); + + /** + * Stream of {@link Identifier} representing the Collections of given domainType, including mixed-in ones. + * @since 4.0 + */ + Stream streamCollections(@Nullable Class domainType); } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/value/Listing.java b/api/applib/src/main/java/org/apache/causeway/applib/util/Listing.java similarity index 97% rename from api/applib/src/main/java/org/apache/causeway/applib/value/Listing.java rename to api/applib/src/main/java/org/apache/causeway/applib/util/Listing.java index 8ba539ed56d..773f3dc0c1a 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/value/Listing.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/util/Listing.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.causeway.applib.value; +package org.apache.causeway.applib.util; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -29,7 +29,8 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import org.apache.causeway.applib.annotation.Value; +import org.apache.causeway.applib.annotation.Programmatic; +import org.apache.causeway.applib.annotation.ValueSemantics; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.functional.Try; import org.apache.causeway.commons.internal.base._NullSafe; @@ -40,10 +41,14 @@ * where each {@link Line} has semantics, such as being able to be commented out. * *

Each non-comment line can be mapped to a Java class of type {@code T}. + * + *

Typically not used a domain value, as we provide no {@link ValueSemantics}, + * but rather as a (multi-line) {@link String} support class. + * * @param type each non-comment line can be mapped to * @since 4.0 */ -@Value +@Programmatic public record Listing( @NonNull ListingHandler handler, @NonNull Can lines) { diff --git a/api/applib/src/test/java/org/apache/causeway/applib/value/ListingTest.java b/api/applib/src/test/java/org/apache/causeway/applib/util/ListingTest.java similarity index 97% rename from api/applib/src/test/java/org/apache/causeway/applib/value/ListingTest.java rename to api/applib/src/test/java/org/apache/causeway/applib/util/ListingTest.java index f07b2d064a6..459cfd8c588 100644 --- a/api/applib/src/test/java/org/apache/causeway/applib/value/ListingTest.java +++ b/api/applib/src/test/java/org/apache/causeway/applib/util/ListingTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.causeway.applib.value; +package org.apache.causeway.applib.util; import org.junit.jupiter.api.Test; @@ -25,8 +25,8 @@ import org.springframework.util.StringUtils; -import org.apache.causeway.applib.value.Listing.ListingHandler; -import org.apache.causeway.applib.value.Listing.MergePolicy; +import org.apache.causeway.applib.util.Listing.ListingHandler; +import org.apache.causeway.applib.util.Listing.MergePolicy; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.io.TextUtils; diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java index 6708e308db1..f624bec9882 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/columnorder/ColumnOrderTxtFileServiceDefault.java @@ -18,8 +18,6 @@ */ package org.apache.causeway.core.metamodel.services.columnorder; -import java.util.List; - import jakarta.annotation.Priority; import jakarta.inject.Named; @@ -63,15 +61,6 @@ public byte[] toZip(final Object domainObject) { return zipBuilder.toBytes(); } - @Override - @Programmatic - public List collectionIds(final Object domainObject) { - var objSpec = specificationLoader.loadSpecification(domainObject.getClass()); - return objSpec.streamCollections(MixedIn.INCLUDED) - .map(ObjectFeature::getId) - .toList(); - } - // -- HELPER private void addStandaloneEntry(final ObjectSpecification objSpec, final ZipUtils.EntryBuilder zipBuilder) { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java index b2232bbd58f..40113b7e7dd 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java @@ -25,6 +25,7 @@ import java.util.TreeSet; import java.util.function.BiPredicate; import java.util.stream.Collectors; +import java.util.stream.Stream; import jakarta.annotation.Priority; import jakarta.inject.Named; @@ -36,6 +37,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.PriorityPrecedence; import org.apache.causeway.applib.id.LogicalType; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; @@ -56,6 +58,7 @@ import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; import org.apache.causeway.core.metamodel.facets.members.publish.command.CommandPublishingFacet; import org.apache.causeway.core.metamodel.services.metamodel.MetaModelAnnotator.ExporterConfig; +import org.apache.causeway.core.metamodel.spec.ActionScope; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.MixedIn; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; @@ -172,23 +175,19 @@ protected boolean isBuiltIn(final ObjectSpecification spec) { @Override public BeanSort sortOf( final @Nullable Class domainType, final Mode mode) { - if(domainType == null) { + if(domainType == null) return null; - } final ObjectSpecification objectSpec = specificationLoader().specForType(domainType).orElse(null); - if(objectSpec == null) { + if(objectSpec == null) return BeanSort.UNKNOWN; - } if(objectSpec.getBeanSort().isUnknown() - && !(mode == Mode.RELAXED)) { - + && !(mode == Mode.RELAXED)) throw new IllegalArgumentException(String.format( "Unable to determine what sort of domain object this is: '%s'. Originating domainType: '%s'", objectSpec.getFullIdentifier(), domainType.getName() )); - } return objectSpec.getBeanSort(); @@ -196,28 +195,24 @@ public BeanSort sortOf( @Override public BeanSort sortOf(final Bookmark bookmark, final Mode mode) { - if(bookmark == null) { + if(bookmark == null) return null; - } - final Class domainType; - switch (mode) { - case RELAXED: - domainType = specificationLoader().specForBookmark(bookmark) - .map(ObjectSpecification::getCorrespondingClass) - .orElse(null); - break; - - case STRICT: - // fall through to... - default: - domainType = specificationLoader().specForBookmark(bookmark) - .map(ObjectSpecification::getCorrespondingClass) - .orElseThrow(()->_Exceptions - .noSuchElement("Cannot resolve logical type name %s to a java class", - bookmark.logicalTypeName())); - break; - } + final Class domainType = switch (mode) { + case RELAXED -> specificationLoader().specForBookmark(bookmark) + .map(ObjectSpecification::getCorrespondingClass) + .orElse(null); + case STRICT -> specificationLoader().specForBookmark(bookmark) + .map(ObjectSpecification::getCorrespondingClass) + .orElseThrow(()->_Exceptions + .noSuchElement("Cannot resolve logical type name %s to a java class", + bookmark.logicalTypeName())); + default -> specificationLoader().specForBookmark(bookmark) + .map(ObjectSpecification::getCorrespondingClass) + .orElseThrow(()->_Exceptions + .noSuchElement("Cannot resolve logical type name %s to a java class", + bookmark.logicalTypeName())); + }; return sortOf(domainType, mode); } @@ -227,22 +222,18 @@ public CommandDtoProcessor commandDtoProcessorFor(final String memberIdentifier) .newFeature(ApplicationFeatureSort.MEMBER, memberIdentifier); final String logicalTypeName = featureId.getLogicalTypeName(); - if(_Strings.isNullOrEmpty(logicalTypeName)) { + if(_Strings.isNullOrEmpty(logicalTypeName)) return null; - } final ObjectSpecification spec = specificationLoader().specForLogicalTypeName(logicalTypeName).orElse(null); - if(spec == null) { + if(spec == null) return null; - } final ObjectMember objectMemberIfAny = spec.getMember(featureId.getLogicalMemberName()).orElse(null); - if (objectMemberIfAny == null) { + if (objectMemberIfAny == null) return null; - } final CommandPublishingFacet commandPublishingFacet = objectMemberIfAny.getFacet(CommandPublishingFacet.class); - if(commandPublishingFacet == null) { + if(commandPublishingFacet == null) return null; - } return commandPublishingFacet.getProcessor(); } @@ -274,4 +265,31 @@ public ObjectGraph exportObjectGraph(final @NonNull BiPredicate streamActions(@Nullable final Class domainType) { + return specificationLoader() + .specForType(domainType) + .stream() + .flatMap(spec->spec.streamActions(ActionScope.PRODUCTION_ONLY, MixedIn.INCLUDED)) + .map(ObjectAction::getFeatureIdentifier); + } + + @Override + public Stream streamProperties(@Nullable final Class domainType) { + return specificationLoader() + .specForType(domainType) + .stream() + .flatMap(spec->spec.streamProperties(MixedIn.INCLUDED)) + .map(OneToOneAssociation::getFeatureIdentifier); + } + + @Override + public Stream streamCollections(@Nullable final Class domainType) { + return specificationLoader() + .specForType(domainType) + .stream() + .flatMap(spec->spec.streamCollections(MixedIn.INCLUDED)) + .map(OneToManyAssociation::getFeatureIdentifier); + } + } diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml index 75917e6faf1..ac74dea5ae5 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml @@ -1047,7 +1047,7 @@ - + @@ -1187,7 +1187,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml index 3cc73d588aa..68449dd992f 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml @@ -1040,7 +1040,7 @@ - + @@ -1180,7 +1180,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml index 3c2628e73d6..4ae637ccaa5 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml @@ -1036,7 +1036,7 @@ - + @@ -1176,7 +1176,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml index 8fd339014a9..16767b5441d 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml @@ -1029,7 +1029,7 @@ - + @@ -1169,7 +1169,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index c3c5fcec45c..71922ce1f7a 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -904,7 +904,7 @@ - + @@ -1028,7 +1028,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -2126,7 +2126,7 @@ - + @@ -2250,7 +2250,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -3593,7 +3593,7 @@ - + @@ -3717,7 +3717,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -4887,7 +4887,7 @@ - + @@ -5011,7 +5011,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -6169,7 +6169,7 @@ - + @@ -6293,7 +6293,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -7764,7 +7764,7 @@ - + @@ -7888,7 +7888,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -9497,7 +9497,7 @@ - + @@ -9621,7 +9621,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -11422,7 +11422,7 @@ - + @@ -11546,7 +11546,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -12830,7 +12830,7 @@ - + @@ -12954,7 +12954,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -14222,7 +14222,7 @@ - + @@ -14346,7 +14346,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -15636,7 +15636,7 @@ - + @@ -15760,7 +15760,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -17075,7 +17075,7 @@ - + @@ -17199,7 +17199,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -18577,7 +18577,7 @@ - + @@ -18701,7 +18701,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -20239,7 +20239,7 @@ - + @@ -20363,7 +20363,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -21830,7 +21830,7 @@ - + @@ -21954,7 +21954,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -23132,7 +23132,7 @@ - + @@ -23256,7 +23256,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -24434,7 +24434,7 @@ - + @@ -24558,7 +24558,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -28552,7 +28552,7 @@ - + @@ -28676,7 +28676,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -29985,7 +29985,7 @@ - + @@ -30109,7 +30109,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -31650,7 +31650,7 @@ - + @@ -31774,7 +31774,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -33319,7 +33319,7 @@ - + @@ -33443,7 +33443,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -38710,7 +38710,7 @@ - + @@ -38834,7 +38834,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -41633,7 +41633,7 @@ - + @@ -41757,7 +41757,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -42930,7 +42930,7 @@ - + @@ -43054,7 +43054,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -44316,7 +44316,7 @@ - + @@ -44440,7 +44440,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -46040,7 +46040,7 @@ - + @@ -46164,7 +46164,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -47686,7 +47686,7 @@ - + @@ -47810,7 +47810,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -49332,7 +49332,7 @@ - + @@ -49456,7 +49456,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -51024,7 +51024,7 @@ - + @@ -51148,7 +51148,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId @@ -52482,7 +52482,7 @@ - + @@ -52606,7 +52606,7 @@ - java.lang.String + org.apache.causeway.applib.services.appfeat.ApplicationFeatureId From 7b14a93c71be8f7b4c0bc07ee105c48429e20003 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Wed, 29 Apr 2026 09:18:38 +0200 Subject: [PATCH 06/18] CAUSEWAY-3997: adds converter for ApplicationFeatureId -> Identifier --- .../services/appfeat/ApplicationFeature.java | 8 +- .../appfeat/ApplicationFeatureId.java | 59 ++++------- .../appfeat/ApplicationFeatureRepository.java | 4 + .../apache/causeway/applib/util/Listing.java | 8 +- .../ApplicationFeatureRepositoryDefault.java | 100 ++++++++++-------- 5 files changed, 90 insertions(+), 89 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeature.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeature.java index d0b6f043f5a..16ded0af52d 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeature.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeature.java @@ -44,10 +44,10 @@ default String getFullyQualifiedName() { default SortedSet getMembersOfSort(final ApplicationMemberSort memberSort) { return switch (memberSort) { - case PROPERTY -> getProperties(); - case COLLECTION -> getCollections(); - case ACTION -> getActions(); - default -> Collections.emptySortedSet(); + case PROPERTY -> getProperties(); + case COLLECTION -> getCollections(); + case ACTION -> getActions(); + default -> Collections.emptySortedSet(); }; } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java index e748deb42df..a2d1dc54bfc 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java @@ -26,6 +26,7 @@ import static java.util.Comparator.naturalOrder; import static java.util.Comparator.nullsFirst; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.Identifier; @@ -45,7 +46,6 @@ import org.apache.causeway.commons.internal.exceptions._Exceptions; import lombok.Getter; -import org.jspecify.annotations.NonNull; import lombok.Synchronized; /** @@ -83,9 +83,8 @@ public static ApplicationFeatureId fromIdentifier(final @NonNull Identifier iden var logicalTypeName = identifier.logicalTypeName(); - if(identifier.type().isClass()) { + if(identifier.type().isClass()) return newType(logicalTypeName); - } return newMember(logicalTypeName, identifier.memberLogicalName()); } @@ -93,15 +92,11 @@ public static ApplicationFeatureId newFeature( final @NonNull ApplicationFeatureSort featureSort, final @NonNull String qualifiedLogicalName) { - switch (featureSort) { - case NAMESPACE: - return newNamespace(qualifiedLogicalName); - case TYPE: - return newType(qualifiedLogicalName); - case MEMBER: - return newMember(qualifiedLogicalName); - } - throw _Exceptions.illegalArgument("Unknown feature sort '%s'", featureSort); + return switch (featureSort) { + case NAMESPACE -> newNamespace(qualifiedLogicalName); + case TYPE -> newType(qualifiedLogicalName); + case MEMBER -> newMember(qualifiedLogicalName); + }; } public static ApplicationFeatureId newFeature( @@ -109,13 +104,11 @@ public static ApplicationFeatureId newFeature( final @Nullable String logicalTypeSimpleName, final @Nullable String memberName) { - if(logicalTypeSimpleName == null) { + if(logicalTypeSimpleName == null) return newNamespace(namespace); - } var logicalTypeName = namespace + "." + logicalTypeSimpleName; - if(memberName == null) { + if(memberName == null) return newType(logicalTypeName); - } return newMember(logicalTypeName, memberName); } @@ -161,9 +154,8 @@ public static ApplicationFeatureId newMember(final String logicalTypeName, final public static ApplicationFeatureId newMember(final String fullyQualifiedLogicalName) { var featureId = new ApplicationFeatureId(ApplicationFeatureSort.MEMBER); var i = fullyQualifiedLogicalName.lastIndexOf("#"); - if(i == -1) { + if(i == -1) throw new IllegalArgumentException("Malformed, expected a '#': " + fullyQualifiedLogicalName); - } var logicalTypeName = fullyQualifiedLogicalName.substring(0, i); var memberName = fullyQualifiedLogicalName.substring(i + 1); initType(featureId, logicalTypeName); @@ -184,10 +176,9 @@ private static void initType(final ApplicationFeatureId featureId, final String } // guard against empty namespace; there should be a meta-model validator that already catched that - if(_Strings.isEmpty(featureId.namespace)) { + if(_Strings.isEmpty(featureId.namespace)) throw _Exceptions.illegalArgument( "fullyQualifiedName '%s' must include a non-empty namespace", fullyQualifiedName); - } featureId.logicalMemberName = null; } @@ -197,9 +188,8 @@ private static void initMember(final ApplicationFeatureId featureId, final @Null } private static String stripOffParamsIfAny(final @Nullable String name) { - if(_Strings.isEmpty(name)) { + if(_Strings.isEmpty(name)) return name; - } final int paramListStartIndex = name.indexOf('('); return paramListStartIndex>-1 ? name.substring(0, paramListStartIndex) @@ -284,9 +274,8 @@ public String getFullyQualifiedName() { @Programmatic public String getLogicalTypeName() { - if (getTypeSimpleName() == null) { + if (getTypeSimpleName() == null) return null; - } var buf = new StringBuilder(); if(!_Strings.isNullOrEmpty(getNamespace())) { buf.append(getNamespace()).append("."); @@ -305,13 +294,12 @@ public ApplicationFeatureId getParentNamespaceFeatureId() { _Assert.assertFalse(sort.isMember()); - if(sort.isType()) { + if(sort.isType()) return ApplicationFeatureId.newNamespace(getNamespace()); - } else { + else { var namespace = getNamespace(); // eg aaa.bbb.ccc - if(!namespace.contains(".")) { + if(!namespace.contains(".")) return null; // parent is root - } final int cutOffPos = namespace.lastIndexOf('.'); final String parentPackageName = namespace.substring(0, cutOffPos); return newNamespace(parentPackageName); @@ -479,15 +467,12 @@ public ApplicationFeatureId withNamespace(final @NonNull String namespace) { * @param logicalTypeName */ public ApplicationFeatureId withLogicalTypeName(final @NonNull String logicalTypeName) { - switch (getSort()) { - case MEMBER: - return newMember(logicalTypeName, this.getLogicalMemberName()); - case TYPE: - return newType(logicalTypeName); - case NAMESPACE: - default: - return this; - } + return switch (getSort()) { + case MEMBER -> newMember(logicalTypeName, this.getLogicalMemberName()); + case TYPE -> newType(logicalTypeName); + case NAMESPACE -> this; + default -> this; + }; } } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java index dbcde0143c8..7c8525c6554 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java @@ -20,8 +20,10 @@ import java.util.Collection; import java.util.Map; +import java.util.Optional; import java.util.SortedSet; +import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.id.LogicalType; /** @@ -52,4 +54,6 @@ public interface ApplicationFeatureRepository { Collection allMembers(); SortedSet propertyIdsFor(LogicalType logicalType); + + Optional asIdentifier(ApplicationFeatureId applicationFeatureId); } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/util/Listing.java b/api/applib/src/main/java/org/apache/causeway/applib/util/Listing.java index 773f3dc0c1a..cb3b5d0ca2e 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/util/Listing.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/util/Listing.java @@ -42,10 +42,10 @@ * *

Each non-comment line can be mapped to a Java class of type {@code T}. * - *

Typically not used a domain value, as we provide no {@link ValueSemantics}, + *

Typically not used as domain value, as we provide no {@link ValueSemantics}, * but rather as a (multi-line) {@link String} support class. * - * @param type each non-comment line can be mapped to + * @param type each non-comment line can be mapped to (successful parsing) * @since 4.0 */ @Programmatic @@ -88,8 +88,8 @@ public Listing parseListing(@Nullable final Can textLines) { /** * Parses the whole line as is, including comments. - *

- * If a line is not a comment, but can also not be mapped to {@code T}, + * + *

If a line is not a comment, but can also not be mapped to {@code T}, * then it is commented out. */ public Line parseLine(final String wholeLine) { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java index d286c6f943a..57af31a44e1 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java @@ -33,9 +33,12 @@ import jakarta.inject.Inject; import jakarta.inject.Named; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; + import org.springframework.stereotype.Service; +import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.SemanticsOf; import org.apache.causeway.applib.events.metamodel.MetamodelListener; import org.apache.causeway.applib.id.LogicalType; @@ -45,7 +48,6 @@ import org.apache.causeway.applib.services.appfeat.ApplicationFeatureSort; import org.apache.causeway.applib.services.appfeat.ApplicationMemberSort; import org.apache.causeway.commons.internal.collections._Maps; -import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.config.metamodel.services.ApplicationFeaturesInitConfiguration; import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; @@ -62,7 +64,6 @@ import org.apache.causeway.core.metamodel.spec.feature.ObjectMember; import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; -import org.jspecify.annotations.NonNull; import lombok.extern.slf4j.Slf4j; /** @@ -123,9 +124,8 @@ enum InitializationState { private InitializationState initializationState = InitializationState.NOT_INITIALIZED; private synchronized void initializeIfRequired() { - if(initializationState == InitializationState.INITIALIZED) { + if(initializationState == InitializationState.INITIALIZED) return; - } initializationState = InitializationState.INITIALIZED; for (var spec : specificationLoader.snapshotSpecifications()) { @@ -147,9 +147,8 @@ private void visitFeatureIdentifierByName( void createApplicationFeaturesFor(final ObjectSpecification spec) { - if (exclude(spec)) { + if (exclude(spec)) return; - } final List properties = spec.streamProperties(MixedIn.INCLUDED) .collect(Collectors.toList()); @@ -158,9 +157,8 @@ void createApplicationFeaturesFor(final ObjectSpecification spec) { final List actions = spec.streamAnyActions(MixedIn.INCLUDED) .collect(Collectors.toList()); - if (properties.isEmpty() && collections.isEmpty() && actions.isEmpty()) { + if (properties.isEmpty() && collections.isEmpty() && actions.isEmpty()) return; - } var logicalType = spec.logicalType(); var logicalTypeName = logicalType.logicalName(); @@ -212,8 +210,9 @@ private static Class correspondingClassFor(final ObjectSpecification objectSp private static Integer valueOf( final FacetHolder facetHolder, final Class cls) { - final SingleIntValueFacet facet = facetHolder.getFacet(cls); - return facet != null ? facet.value() : null; + return facetHolder.lookupFacet(cls) + .map(SingleIntValueFacet::value) + .orElse(null); } ApplicationFeatureId addClassParent(final ApplicationFeatureId classFeatureId) { @@ -226,9 +225,8 @@ ApplicationFeatureId addClassParent(final ApplicationFeatureId classFeatureId) { void addParents(final ApplicationFeatureId classOrPackageId) { final ApplicationFeatureId parentPackageId = classOrPackageId.getParentNamespaceFeatureId(); - if (parentPackageId == null) { + if (parentPackageId == null) return; - } final ApplicationFeatureDefault parentPackage = (ApplicationFeatureDefault)findPackageElseCreate(parentPackageId); @@ -288,9 +286,8 @@ private boolean newMember( final Integer maxLength, final Integer typicalLength, final SemanticsOf actionSemantics) { - if (objectMember.isAlwaysHidden()) { + if (objectMember.isAlwaysHidden()) return false; - } newMember(classFeatureId, objectMember.getId(), memberSort, returnType, derived, maxLength, typicalLength, actionSemantics); return true; } @@ -325,14 +322,11 @@ private void newMember( } private SortedMap featuresMapFor(final ApplicationMemberSort memberSort) { - switch (memberSort) { - case PROPERTY: - return propertyFeatures; - case COLLECTION: - return collectionFeatures; - default: // case ACTION: - return actionFeatures; - } + return switch (memberSort) { + case PROPERTY->propertyFeatures; + case COLLECTION->collectionFeatures; + default->actionFeatures; // case ACTION: + }; } protected boolean exclude(final ObjectSpecification spec) { @@ -379,15 +373,11 @@ public ApplicationFeature newApplicationFeature(final ApplicationFeatureId featI @Override public ApplicationFeature findFeature(final ApplicationFeatureId featureId) { initializeIfRequired(); - switch (featureId.getSort()) { - case NAMESPACE: - return findNamespace(featureId); - case TYPE: - return findLogicalType(featureId); - case MEMBER: - return findMember(featureId); - } - throw _Exceptions.illegalArgument("Feature of unknown sort '%s'", featureId.getSort()); + return switch (featureId.getSort()) { + case NAMESPACE -> findNamespace(featureId); + case TYPE -> findLogicalType(featureId); + case MEMBER -> findMember(featureId); + }; } public ApplicationFeature findNamespace(final ApplicationFeatureId featureId) { @@ -409,18 +399,13 @@ public ApplicationFeature findMember(final ApplicationFeatureId featureId) { public Collection allFeatures(final ApplicationFeatureSort featureType) { initializeIfRequired(); - if (featureType == null) { + if (featureType == null) return Collections.emptyList(); - } - switch (featureType) { - case NAMESPACE: - return allNamespaces(); - case TYPE: - return allTypes(); - case MEMBER: - return allMembers(); - } - throw new IllegalArgumentException("Unknown feature type " + featureType); + return switch (featureType) { + case NAMESPACE -> allNamespaces(); + case TYPE -> allTypes(); + case MEMBER -> allMembers(); + }; } @Override @@ -445,9 +430,8 @@ public Collection allMembers() { public SortedSet propertyIdsFor(final LogicalType logicalType) { initializeIfRequired(); ApplicationFeatureId typeFeatureId = typeFeatureIdByLogicalType.get(logicalType); - if (typeFeatureId == null) { + if (typeFeatureId == null) return Collections.emptySortedSet(); - } ApplicationFeature applicationFeature = typeFeatures.get(typeFeatureId); return applicationFeature.getProperties(); } @@ -476,4 +460,32 @@ public Map getFeatureIdentifiersByName() { return featureIdentifiersByName; } + @Override + public Optional asIdentifier(final ApplicationFeatureId applicationFeatureId) { + return applicationFeatureId!=null + ? specificationLoader.specForLogicalTypeName(applicationFeatureId.getLogicalTypeName()) + .map(ObjectSpecification::logicalType) + .map(logicalType->toIdentifier(applicationFeatureId, logicalType)) + : Optional.empty(); + } + + // -- HELPER + + private Identifier toIdentifier(final ApplicationFeatureId featureId, final LogicalType logicalType) { + return switch (featureId.getSort()) { + case MEMBER -> Optional.ofNullable(findMember(featureId)) + .flatMap(ApplicationFeature::getMemberSort) + .map(memberSort->switch (memberSort) { + case PROPERTY -> Identifier.propertyIdentifier(logicalType, featureId.getLogicalMemberName()); + case COLLECTION -> Identifier.collectionIdentifier(logicalType, featureId.getLogicalMemberName()); + case ACTION -> Identifier.actionIdentifier(logicalType, featureId.getLogicalMemberName()); + default -> null; + }) + .orElse(null); + case TYPE -> Identifier.classIdentifier(logicalType); + case NAMESPACE -> null; + default -> null; + }; + } + } From d624f3a49fe78799be439a560d750553d1c8d8fe Mon Sep 17 00:00:00 2001 From: andi-huber Date: Wed, 29 Apr 2026 09:28:52 +0200 Subject: [PATCH 07/18] CAUSEWAY-3997: todo remarks --- .../services/columnorder/Object_patchColumnOrder.java | 10 ++++++---- .../services/metamodel/MetaModelServiceDefault.java | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java index e8273008183..0694ed00b6d 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -82,18 +82,20 @@ public static class ActionDomainEvent @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.PRESERVE_CHANGES) @ParameterLayout(multiLine = 20) final String columnDefinition) { - // TODO flesh out + // TODO flesh out: we need some holder of column order overrides (patches) return mixee; } @MemberSupport public List choicesCollectionId() { return metaModelService.streamCollections(mixee.getClass()) - .map(ApplicationFeatureId::fromIdentifier) - .toList(); + .map(ApplicationFeatureId::fromIdentifier) + .toList(); } @MemberSupport public String defaultColumnDefinition() { - // TODO flesh out - using Listing + // TODO flesh out using Listing; we need 2 sources + // 1) all column candidates + // 2) all columns currently configured return ""; } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java index 40113b7e7dd..00d3637b7d9 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java @@ -231,7 +231,8 @@ public CommandDtoProcessor commandDtoProcessorFor(final String memberIdentifier) final ObjectMember objectMemberIfAny = spec.getMember(featureId.getLogicalMemberName()).orElse(null); if (objectMemberIfAny == null) return null; - final CommandPublishingFacet commandPublishingFacet = objectMemberIfAny.getFacet(CommandPublishingFacet.class); + final CommandPublishingFacet commandPublishingFacet = objectMemberIfAny.lookupFacet(CommandPublishingFacet.class) + .orElse(null); if(commandPublishingFacet == null) return null; return commandPublishingFacet.getProcessor(); From 031cae7f50c450ff051bdca977e1cf765680a27c Mon Sep 17 00:00:00 2001 From: andi-huber Date: Mon, 11 May 2026 13:33:56 +0200 Subject: [PATCH 08/18] CAUSEWAY-3997: formal API into MM in the context of column ordering --- .../appfeat/ApplicationFeatureId.java | 16 ++- .../appfeat/ApplicationFeatureRepository.java | 4 +- .../columnorder/Object_patchColumnOrder.java | 33 ++++- .../services/metamodel/MetaModelService.java | 29 +++++ .../ApplicationFeatureRepositoryDefault.java | 2 +- .../metamodel/MetaModelServiceDefault.java | 61 +++++++++ .../TableColumnOrderServiceUsingTxtFile.java | 71 ++++------- .../spec/impl/_MembersAsColumns.java | 93 +++++++------- ...sViewer_IntegTest.dump_facets.approved.xml | 4 +- ...nDomain_IntegTest.dump_facets.approved.xml | 4 +- ...sViewer_IntegTest.dump_facets.approved.xml | 4 +- ...pDomain_IntegTest.dump_facets.approved.xml | 4 +- ...etaModelRegressionTest.verify.approved.xml | 120 +++++++++--------- 13 files changed, 272 insertions(+), 173 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java index a2d1dc54bfc..421ffbcd11e 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java @@ -50,16 +50,16 @@ /** * Value type representing a namespace, type or member. - *

- * This value is {@link Comparable}, the implementation of which considers + * + *

This value is {@link Comparable}, the implementation of which considers * {@link #getSort() (feature) sort}, {@link #getNamespace() namespace}, * {@link #getTypeSimpleName() type simple name} and {@link #getLogicalMemberName() member-logical-name}. - *

- * If the represented member is an action, then {@link #getLogicalMemberName() member-logical-name} + * + *

If the represented member is an action, then {@link #getLogicalMemberName() member-logical-name} * must not include any parameter list or parentheses. * Consequently method overloading is not supported. - *

- * If there is a member name clash involving an action and an association, + * + *

If there is a member name clash involving an action and an association, * then consequently any permissions defined automatically apply to both and one cannot separate * these. * @@ -79,6 +79,10 @@ public class ApplicationFeatureId // -- FACTORY METHODS + /** + * @apiNote the inverse conversion is available via + * {@link ApplicationFeatureRepository#asIdentifier(ApplicationFeatureId)} + */ public static ApplicationFeatureId fromIdentifier(final @NonNull Identifier identifier) { var logicalTypeName = identifier.logicalTypeName(); diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java index 7c8525c6554..21eb8551a9c 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java @@ -23,6 +23,8 @@ import java.util.Optional; import java.util.SortedSet; +import org.jspecify.annotations.Nullable; + import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.id.LogicalType; @@ -55,5 +57,5 @@ public interface ApplicationFeatureRepository { SortedSet propertyIdsFor(LogicalType logicalType); - Optional asIdentifier(ApplicationFeatureId applicationFeatureId); + Optional asIdentifier(@Nullable ApplicationFeatureId applicationFeatureId); } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java index 0694ed00b6d..9d46deca11e 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -19,9 +19,11 @@ package org.apache.causeway.applib.services.columnorder; import java.util.List; +import java.util.stream.Collectors; import jakarta.inject.Inject; +import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.Action; import org.apache.causeway.applib.annotation.ActionLayout; import org.apache.causeway.applib.annotation.DomainObject; @@ -37,6 +39,7 @@ import org.apache.causeway.applib.annotation.SemanticsOf; import org.apache.causeway.applib.layout.LayoutConstants; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; +import org.apache.causeway.applib.services.appfeat.ApplicationFeatureRepository; import org.apache.causeway.applib.services.metamodel.MetaModelService; import lombok.RequiredArgsConstructor; @@ -70,6 +73,7 @@ public static class ActionDomainEvent extends org.apache.causeway.applib.CausewayModuleApplib.ActionDomainEvent {} @Inject MetaModelService metaModelService; + @Inject ApplicationFeatureRepository applicationFeatureRepository; private final Object mixee; @@ -79,10 +83,13 @@ public static class ActionDomainEvent + "If 'none', patches all standalone tables, " + "where this domain object type is the element type.") final ApplicationFeatureId collectionId, - @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.PRESERVE_CHANGES) + @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.RESET) @ParameterLayout(multiLine = 20) final String columnDefinition) { // TODO flesh out: we need some holder of column order overrides (patches) + // make sure, + // org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.streamAssociationsForColumnRendering(Identifier, ManagedObject) + // honors any patches. Also patching must override any SPI? return mixee; } @@ -92,11 +99,25 @@ public static class ActionDomainEvent .toList(); } - @MemberSupport public String defaultColumnDefinition() { - // TODO flesh out using Listing; we need 2 sources - // 1) all column candidates - // 2) all columns currently configured - return ""; + @MemberSupport public String defaultColumnDefinition(final ApplicationFeatureId collectionId) { + var memberId = applicationFeatureRepository.asIdentifier(collectionId) + .orElse(null); // not found or none selected (yet) + + // all column candidates + var available = metaModelService.streamAvailableAssociationsForColumnRendering(mixee.getClass(), memberId) + .map(Identifier::memberLogicalName) + .collect(Collectors.joining("\n")); + + // all columns currently configured + var enabled = metaModelService.streamEnabledAssociationsForColumnRendering(mixee.getClass(), memberId, mixee) + .map(Identifier::memberLogicalName) + .collect(Collectors.joining("\n")); + + // TODO flesh out using Listing + return "# available\n" + + available + + "\n\n# enabled\n" + + enabled; } } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java index a274ba0b448..bf1b22675ae 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java @@ -194,4 +194,33 @@ enum Mode { */ Stream streamCollections(@Nullable Class domainType); + /** + * Stream of {@link Identifier} representing the Columns of given domainType and Member of interest. + * + *

Where columns returned are those that are in principle available. Also those are returned in no particular order. + * + *

If domainType is null returns an empty {@link Stream}. + * + *

Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType. + * + * @since 4.0 + */ + Stream streamAvailableAssociationsForColumnRendering(@Nullable Class domainType, @Nullable Identifier memberIdentifier); + + /** + * Stream of {@link Identifier} representing the Columns of given domainType and Member of interest. + * + *

Where columns returned are those that are currently visible. Also those are returned in same order as rendered. + * + *

If domainType is null returns an empty {@link Stream}. + * + *

Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType. + * + * @since 4.0 + */ + Stream streamEnabledAssociationsForColumnRendering( + @Nullable Class domainType, + @Nullable Identifier memberIdentifier, + @Nullable Object domainObject); + } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java index 57af31a44e1..0ef73c34f53 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java @@ -461,7 +461,7 @@ public Map getFeatureIdentifiersByName() { } @Override - public Optional asIdentifier(final ApplicationFeatureId applicationFeatureId) { + public Optional asIdentifier(final @Nullable ApplicationFeatureId applicationFeatureId) { return applicationFeatureId!=null ? specificationLoader.specForLogicalTypeName(applicationFeatureId.getLogicalTypeName()) .map(ObjectSpecification::logicalType) diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java index 00d3637b7d9..f1450a0fc8a 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java @@ -24,6 +24,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.BiPredicate; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -52,16 +53,19 @@ import org.apache.causeway.applib.services.metamodel.MetaModelService; import org.apache.causeway.applib.services.metamodel.objgraph.ObjectGraph; import org.apache.causeway.commons.collections.Can; +import org.apache.causeway.commons.functional.Either; import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.collections._Lists; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; +import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.core.metamodel.facets.members.publish.command.CommandPublishingFacet; import org.apache.causeway.core.metamodel.services.metamodel.MetaModelAnnotator.ExporterConfig; import org.apache.causeway.core.metamodel.spec.ActionScope; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.MixedIn; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; +import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation; import org.apache.causeway.core.metamodel.spec.feature.ObjectMember; import org.apache.causeway.core.metamodel.spec.feature.OneToManyAssociation; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; @@ -293,4 +297,61 @@ public Stream streamCollections(@Nullable final Class domainType) .map(OneToManyAssociation::getFeatureIdentifier); } + @Override + public Stream streamAvailableAssociationsForColumnRendering( + @Nullable final Class domainType, + @Nullable final Identifier memberIdentifier) { + + var elementType = lookupColumnHolder(domainType, memberIdentifier) + .map(either->either.fold(OneToManyAssociation::getElementType, UnaryOperator.identity())) + .orElse(null); + + if(elementType==null) + return Stream.empty(); + + return elementType.streamAssociations(MixedIn.INCLUDED) + .map(ObjectAssociation::getFeatureIdentifier); + } + + @Override + public Stream streamEnabledAssociationsForColumnRendering( + @Nullable final Class domainType, + @Nullable final Identifier memberIdentifier, + @Nullable final Object domainObject) { + + var elementType = lookupColumnHolder(domainType, memberIdentifier) + .map(either->either.fold(OneToManyAssociation::getElementType, UnaryOperator.identity())) + .orElse(null); + + if(elementType==null) + return Stream.empty(); + + var mo = MetaModelContext.instanceElseFail() + .getObjectManager() + .adapt(domainObject); + + return elementType.streamAssociationsForColumnRendering(memberIdentifier, mo) + .map(ObjectAssociation::getFeatureIdentifier); + } + + // -- HELPER + + private Optional> lookupColumnHolder( + @Nullable final Class domainType, + @Nullable final Identifier memberIdentifier) { + + var domObjSpec = specificationLoader() + .specForType(domainType) + .orElse(null); + + if(domObjSpec == null) + return Optional.empty(); + + if(memberIdentifier == null) + return Optional.of(Either.right(domObjSpec)); + + return domObjSpec.getCollection(memberIdentifier.memberLogicalName()) + .map(Either::left); + } + } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java index 7ee868be64c..30d2fb47aca 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java @@ -26,8 +26,10 @@ import jakarta.annotation.Priority; import jakarta.inject.Named; -import org.springframework.beans.factory.annotation.Qualifier; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; + +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.apache.causeway.applib.annotation.CollectionLayout; @@ -38,7 +40,6 @@ import org.apache.causeway.commons.io.TextUtils; import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; -import org.jspecify.annotations.NonNull; import lombok.extern.slf4j.Slf4j; /** @@ -46,8 +47,7 @@ * file containing the list of the associations (usually properties, but collections are also supported), in the * desired order, one associationId per line. * - *

- * The files are located relative to the class itself. A number of conventions are supported: + *

The files are located relative to the class itself. A number of conventions are supported: * *

    *
  • @@ -74,19 +74,15 @@ *
  • *
* - *

- * Any associations omitted from the file will not be shown as columns of the table. The associationId must also + *

Any associations omitted from the file will not be shown as columns of the table. The associationId must also * be an exact match, so can be ignored by commenting out, eg with "#". - *

* - *

- * Also note that association that have been explicitly hidden from tables using - * {@link PropertyLayout#hidden() @PropertyLayout#hidden} or {@link CollectionLayout#hidden()} are never shown, - * irrespective of whether they are listed in the files. You may therefore prefer to not hide properties - * with annotations, and then rely solely on these external columnOrder.txt files. This has the further - * benefit that files can be modified at runtime and will be automatically picked up without requiring a restart - * of the application. - *

+ *

Also note that association that have been explicitly hidden from tables using + * {@link PropertyLayout#hidden() @PropertyLayout#hidden} or {@link CollectionLayout#hidden()} are never shown, + * irrespective of whether they are listed in the files. You may therefore prefer to not hide properties + * with annotations, and then rely solely on these external columnOrder.txt files. This has the further + * benefit that files can be modified at runtime and will be automatically picked up without requiring a restart + * of the application. * * @see TableColumnOrderServiceDefault * @@ -102,8 +98,7 @@ public class TableColumnOrderServiceUsingTxtFile implements TableColumnOrderServ /** * Reads association Ids of the parented collection from a file. * - *

- * The search algorithm is: + *

The search algorithm is: *

    *
  • ParentClassName#collectionId.columnOrder.txt
  • *
  • ParentClassName#collectionId.columnOrder.fallback.txt
  • @@ -116,11 +111,8 @@ public class TableColumnOrderServiceUsingTxtFile implements TableColumnOrderServ *
  • ElementTypeClassName.columnOrder.txt
  • *
  • ElementTypeClassName.columnOrder.fallback.txt
  • *
- *

* - *

- * Additional files can be provided by overriding {@link #addResourceNames(Class, String, Class, List)} - *

+ *

Additional files can be provided by overriding {@link #addResourceNames(Class, String, Class, List)} * * @Returns {@code null}, if no matching resource was found */ @@ -135,9 +127,9 @@ public List orderParented( var domainClass = domainObject.getClass(); var resourceNames = buildResourceNames(domainClass, collectionId, elementType); addResourceNames(elementType, resourceNames); // fallback to reading the element type's own .txt file. - var contents = tryLoad(domainClass, resourceNames) + var content = tryLoad(domainClass, resourceNames) .orElse(null); - return contentsMatching(contents, associationIds); + return contentMatching(content, associationIds); } private List buildResourceNames( @@ -152,9 +144,7 @@ private List buildResourceNames( /** * Builds the list of file names to be read from. * - *

- * The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt. - *

+ *

The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt. * * @param domainClass - the class with the parent collection * @param collectionId - the id of the collection @@ -175,9 +165,8 @@ private static Optional tryLoad(final Class domainClass, final List tryLoad(final Class domainClass, final List - * The search algorithm is: + *

The search algorithm is: *

    *
  • DomainTypeClassName.columnOrder.txt
  • *
  • DomainTypeClassName.columnOrder.fallback.txt
  • *
- *

* - *

- * Additional files can be provided by overriding {@link #addResourceNames(Class, List)}. - *

+ *

Additional files can be provided by overriding {@link #addResourceNames(Class, List)}. * * @Returns {@code null}, if no matching resource was found */ @@ -212,9 +197,9 @@ public List orderStandalone( final Class domainType, final List associationIds) { var resourceNames = buildResourceNames(domainType); - var contents = tryLoad(domainType, resourceNames) + var content = tryLoad(domainType, resourceNames) .orElse(null); - return contentsMatching(contents, associationIds); + return contentMatching(content, associationIds); } private List buildResourceNames(final Class domainClass) { @@ -226,9 +211,7 @@ private List buildResourceNames(final Class domainClass) { /** * Builds the list of file names to be read from. * - *

- * The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt. - *

+ *

The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt. * * @param domainClass - the class in the standalone collection * @param addTo - to be added to @@ -241,15 +224,15 @@ protected void addResourceNames( } /** - * if contents is {@code null} returns {@code null} + * if content is {@code null} returns {@code null} */ @Nullable - private static List contentsMatching( - final @Nullable String contents, + private static List contentMatching( + final @Nullable String content, final @NonNull List associationIds) { - return contents==null + return content==null ? null - : TextUtils.readLines(contents).stream() + : TextUtils.readLines(content).stream() .map(String::trim) // ignore any leading or trailing whitespace .filter(line->!line.startsWith("#")) // speed up (not strictly required) .filter(associationIds::contains) diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java index b425e8ac036..8967665f058 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java @@ -28,6 +28,7 @@ import java.util.stream.Stream; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.Where; @@ -45,9 +46,6 @@ import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation; import org.apache.causeway.core.metamodel.util.WhereContexts; -import static org.apache.causeway.applib.annotation.Where.PARENTED_TABLES; -import static org.apache.causeway.applib.annotation.Where.STANDALONE_TABLES; - import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -63,7 +61,7 @@ public final Stream streamActionsForColumnRendering( if(elementType.isValue()) return Stream.empty(); return elementType.streamRuntimeActions(MixedIn.INCLUDED) - .filter(ObjectAction.Predicates.visibleAccordingToHiddenFacet(WhereContexts.collectionVariant(memberIdentifier))) + .filter(ObjectAction.Predicates.visibleAccordingToHiddenFacet(Where.STANDALONE_TABLES)) .sorted((a, b)->a.getCanonicalFriendlyName().compareTo(b.getCanonicalFriendlyName())); } @@ -71,31 +69,39 @@ public final Stream streamActionsForColumnRendering( * @param parentObject not used for standalone tables and allowed to be empty for parented ones */ public final Stream streamAssociationsForColumnRendering( + // the type that has the properties and collections that make up this table's columns final ObjectSpecification elementType, - final Identifier memberIdentifier, + // if null corresponds to a standalone table + final @Nullable Identifier memberIdentifier, final ManagedObject parentObject) { - // the type that has the properties and collections that make up this table's columns - var elementClass = elementType.getCorrespondingClass(); - - var parentSpecIfAny = parentObject.objSpec(); - var assocById = new LinkedHashMap(); - - elementType.streamAssociations(MixedIn.INCLUDED) - .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(WhereContexts.collectionVariant(memberIdentifier))) - .filter(ObjectAssociation.Predicates.referencesParent(parentSpecIfAny).negate()) - .filter(assoc->filterColumnsUsingSpi(assoc, elementClass)) // optional SPI to filter columns; - .forEach(assoc->assocById.put(assoc.getId(), assoc)); - - var assocIdsInOrder = new ArrayList(assocById.keySet()); + final Where whereContext; + + if(memberIdentifier==null) { // handle the standalone case + whereContext = Where.STANDALONE_TABLES; + elementType.streamAssociations(MixedIn.INCLUDED) + .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(whereContext)) + .filter(assoc->filterColumnsUsingSpi(assoc, elementType.getCorrespondingClass())) // optional SPI to filter columns; + .forEach(assoc->assocById.put(assoc.getId(), assoc)); + + } else { + whereContext = WhereContexts.collectionVariant(memberIdentifier); + elementType.streamAssociations(MixedIn.INCLUDED) + .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(whereContext)) + .filter(ObjectAssociation.Predicates.referencesParent(parentObject.objSpec()).negate()) + .filter(assoc->filterColumnsUsingSpi(assoc, elementType.getCorrespondingClass())) // optional SPI to filter columns; + .forEach(assoc->assocById.put(assoc.getId(), assoc)); + } + + var assocIdsInOrder = new ArrayList<>(assocById.keySet()); // sort by order of occurrence within associated layout, if any propertyIdComparator(elementType) .ifPresent(assocIdsInOrder::sort); // optional SPI to reorder columns - sortColumnsUsingSpi(memberIdentifier, parentObject, assocIdsInOrder, elementClass); + sortColumnsUsingSpi(whereContext, memberIdentifier, parentObject, assocIdsInOrder, elementType.getCorrespondingClass()); // add all ordered columns to the table return assocIdsInOrder.stream() @@ -121,7 +127,7 @@ private Optional> propertyIdComparator( // same code also appears in DomainObjectPage. // we need to do this here otherwise any tables will render the columns in the wrong order until at least // one object of that type has been rendered via DomainObjectPage. - var elementTypeGridFacet = elementTypeSpec.getFacet(GridFacet.class); + var elementTypeGridFacet = elementTypeSpec.lookupFacet(GridFacet.class).orElse(null); if(elementTypeGridFacet == null) return Optional.empty(); @@ -150,43 +156,36 @@ private Optional> propertyIdComparator( } private void sortColumnsUsingSpi( - final Identifier memberIdentifier, + final Where whereContext, + final @Nullable Identifier memberIdentifier, // not used for standalone tables, and allowed to be empty in parented ones final ManagedObject parentObject, - final List propertyIdsInOrder, + final List assocIdsInOrder, final Class elementType) { var tableColumnOrderServices = getServiceRegistry().select(TableColumnOrderService.class); if(tableColumnOrderServices.isEmpty()) return; - var whereContext = whereContextFor(memberIdentifier); - tableColumnOrderServices.stream() - .map(tableColumnOrderService-> - whereContext.inStandaloneTable() - ? tableColumnOrderService.orderStandalone( - elementType, - propertyIdsInOrder) - : tableColumnOrderService.orderParented( - parentObject.getPojo(), - memberIdentifier.memberLogicalName(), - elementType, - propertyIdsInOrder)) - .filter(_NullSafe::isPresent) - .findFirst() - .filter(propertyReorderedIds->propertyReorderedIds!=propertyIdsInOrder) // skip if its the same object - .ifPresent(propertyReorderedIds->{ - propertyIdsInOrder.clear(); - propertyIdsInOrder.addAll(propertyReorderedIds); - }); - - } + .map(tableColumnOrderService-> + whereContext.inStandaloneTable() + ? tableColumnOrderService.orderStandalone( + elementType, + assocIdsInOrder) + : tableColumnOrderService.orderParented( + parentObject.getPojo(), + memberIdentifier.memberLogicalName(), + elementType, + assocIdsInOrder)) + .filter(_NullSafe::isPresent) + .findFirst() + .filter(assocReorderedIds->assocReorderedIds!=assocIdsInOrder) // skip if its the same object + .ifPresent(assocReorderedIds->{ + assocIdsInOrder.clear(); + assocIdsInOrder.addAll(assocReorderedIds); + }); - static Where whereContextFor(final Identifier memberIdentifier) { - return memberIdentifier.type().isAction() - ? STANDALONE_TABLES - : PARENTED_TABLES; } } diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml index ac74dea5ae5..9d8d19362a5 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml @@ -1223,13 +1223,13 @@ - + - + java.lang.String diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml index 68449dd992f..741dc7f270d 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml @@ -1216,13 +1216,13 @@ - + - + java.lang.String diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml index 4ae637ccaa5..77c22358f40 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml @@ -1212,13 +1212,13 @@ - + - + java.lang.String diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml index 16767b5441d..d0ca7b72317 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml @@ -1205,13 +1205,13 @@ - + - + java.lang.String diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index 71922ce1f7a..99f6257616b 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -1052,13 +1052,13 @@ - + - + java.lang.String @@ -2274,13 +2274,13 @@ - + - + java.lang.String @@ -3741,13 +3741,13 @@ - + - + java.lang.String @@ -5035,13 +5035,13 @@ - + - + java.lang.String @@ -6317,13 +6317,13 @@ - + - + java.lang.String @@ -7912,13 +7912,13 @@ - + - + java.lang.String @@ -9645,13 +9645,13 @@ - + - + java.lang.String @@ -11570,13 +11570,13 @@ - + - + java.lang.String @@ -12978,13 +12978,13 @@ - + - + java.lang.String @@ -14370,13 +14370,13 @@ - + - + java.lang.String @@ -15784,13 +15784,13 @@ - + - + java.lang.String @@ -17223,13 +17223,13 @@ - + - + java.lang.String @@ -18725,13 +18725,13 @@ - + - + java.lang.String @@ -20387,13 +20387,13 @@ - + - + java.lang.String @@ -21978,13 +21978,13 @@ - + - + java.lang.String @@ -23280,13 +23280,13 @@ - + - + java.lang.String @@ -24582,13 +24582,13 @@ - + - + java.lang.String @@ -28700,13 +28700,13 @@ - + - + java.lang.String @@ -30133,13 +30133,13 @@ - + - + java.lang.String @@ -31798,13 +31798,13 @@ - + - + java.lang.String @@ -33467,13 +33467,13 @@ - + - + java.lang.String @@ -38858,13 +38858,13 @@ - + - + java.lang.String @@ -41781,13 +41781,13 @@ - + - + java.lang.String @@ -43078,13 +43078,13 @@ - + - + java.lang.String @@ -44464,13 +44464,13 @@ - + - + java.lang.String @@ -46188,13 +46188,13 @@ - + - + java.lang.String @@ -47834,13 +47834,13 @@ - + - + java.lang.String @@ -49480,13 +49480,13 @@ - + - + java.lang.String @@ -51172,13 +51172,13 @@ - + - + java.lang.String @@ -52630,13 +52630,13 @@ - + - + java.lang.String From bb4f30f918ac6c76872c7e680080288a49cc43b6 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Tue, 12 May 2026 07:11:23 +0200 Subject: [PATCH 09/18] CAUSEWAY-3997: improves design of Object_patchColumnOrder --- .../columnorder/Object_patchColumnOrder.java | 29 +- .../services/metamodel/MetaModelService.java | 18 + .../metamodel/MetaModelServiceDefault.java | 10 + ...sViewer_IntegTest.dump_facets.approved.xml | 18 +- ...nDomain_IntegTest.dump_facets.approved.xml | 18 +- ...sViewer_IntegTest.dump_facets.approved.xml | 18 +- ...pDomain_IntegTest.dump_facets.approved.xml | 18 +- ...etaModelRegressionTest.verify.approved.xml | 540 +++++++++--------- 8 files changed, 354 insertions(+), 315 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java index 9d46deca11e..2d52fbf8228 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import jakarta.inject.Inject; @@ -58,7 +59,7 @@ @ActionLayout( cssClassFa = "solid file-arrow-up", describedAs = "Uploads table column order, to be stored in memory for this object type. " - + "It overrules the default lookup. " + + "It overrules the default column order definition lookup. " + "On application restart this information is lost.", fieldSetId = LayoutConstants.FieldSetId.METADATA, position = ActionLayout.Position.PANEL_DROPDOWN, @@ -78,24 +79,34 @@ public static class ActionDomainEvent private final Object mixee; @MemberSupport public Object act( + @Parameter(optionality = Optionality.OPTIONAL) - @ParameterLayout(describedAs = "The Collection, for which the patch is to be applied (in-memory). " - + "If 'none', patches all standalone tables, " - + "where this domain object type is the element type.") - final ApplicationFeatureId collectionId, + @ParameterLayout(describedAs = "The 'Feature', for which the patch is to be applied (in-memory), " + + "that is, " + + "either for a one-to-many relation (a PARENTED Collection), " + + "or a domain-type (a STANDALONE Collection). " + + "The Feature either represents a particular one-to-many relation of this domain-type or " + + "represents the domain-type itself or one of the domain-type's super types. " + + "If 'none', patches all one-to-many relations of this domain-type (acting as a wildcard). " + + "Wildcard patches have least priority.") + final ApplicationFeatureId featureId, + @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.RESET) @ParameterLayout(multiLine = 20) final String columnDefinition) { // TODO flesh out: we need some holder of column order overrides (patches) // make sure, // org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.streamAssociationsForColumnRendering(Identifier, ManagedObject) - // honors any patches. Also patching must override any SPI? + // honors any patches. Also patching must override any SPI, because that also is a use-case for patching. return mixee; } - @MemberSupport public List choicesCollectionId() { - return metaModelService.streamCollections(mixee.getClass()) - .map(ApplicationFeatureId::fromIdentifier) + @MemberSupport public List choicesFeatureId() { + return Stream.concat( + metaModelService.streamTypeHierarchy(mixee.getClass()) + .map(ApplicationFeatureId::fromIdentifier), + metaModelService.streamCollections(mixee.getClass()) + .map(ApplicationFeatureId::fromIdentifier)) .toList(); } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java index bf1b22675ae..943d521f776 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java @@ -194,6 +194,20 @@ enum Mode { */ Stream streamCollections(@Nullable Class domainType); + /** + * Stream of {@link Identifier} representing the type hierarchy of given domainType, starting at given domainType, + * going 'up' in the hierarchy, but excluding {@link Object}. + * + *

Included are all types in the hierarchy, that are recognized by the Metamodel, recognized Interfaces last. + * + * @apiNote Since 4.0, the Metamodel supports Interfaces acting as element-types for Collections. + * This requires explicit {@link DomainObject} annotations on those Interfaces along with the constraint, that within + * a type hierarchy, every type can have at most one super-type. + * + * @since 4.0 + */ + Stream streamTypeHierarchy(@Nullable Class domainType); + /** * Stream of {@link Identifier} representing the Columns of given domainType and Member of interest. * @@ -203,6 +217,8 @@ enum Mode { * *

Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType. * + *

FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods) + *

FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here * @since 4.0 */ Stream streamAvailableAssociationsForColumnRendering(@Nullable Class domainType, @Nullable Identifier memberIdentifier); @@ -216,6 +232,8 @@ enum Mode { * *

Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType. * + *

FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods) + *

FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here * @since 4.0 */ Stream streamEnabledAssociationsForColumnRendering( diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java index f1450a0fc8a..5158eed0cb1 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java @@ -297,6 +297,16 @@ public Stream streamCollections(@Nullable final Class domainType) .map(OneToManyAssociation::getFeatureIdentifier); } + @Override + public Stream streamTypeHierarchy(@Nullable final Class domainType) { + return specificationLoader() + .specForType(domainType) + .stream() + .flatMap(ObjectSpecification::streamTypeHierarchyAndInterfaces) + .filter(spec->spec.getCorrespondingClass().equals(Object.class)) + .map(ObjectSpecification::getFeatureIdentifier); + } + @Override public Stream streamAvailableAssociationsForColumnRendering( @Nullable final Class domainType, diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml index 9d8d19362a5..d076d082842 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml @@ -1076,9 +1076,9 @@ - + - + @@ -1136,21 +1136,21 @@ java.lang.Object - + - + - + - + - + @@ -1177,8 +1177,8 @@ - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml index 741dc7f270d..6a6c8081842 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml @@ -1069,9 +1069,9 @@ - + - + @@ -1129,21 +1129,21 @@ java.lang.Object - + - + - + - + - + @@ -1170,8 +1170,8 @@ - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml index 77c22358f40..ca386fe7fab 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml @@ -1065,9 +1065,9 @@ - + - + @@ -1125,21 +1125,21 @@ java.lang.Object - + - + - + - + - + @@ -1166,8 +1166,8 @@ - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml index d0ca7b72317..a7325dc7e69 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml @@ -1058,9 +1058,9 @@ - + - + @@ -1118,21 +1118,21 @@ java.lang.Object - + - + - + - + - + @@ -1159,8 +1159,8 @@ - - + + diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index 99f6257616b..7592b451f15 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -933,9 +933,9 @@ - + - + @@ -988,21 +988,21 @@ java.lang.Object - + - + - + - + - + @@ -1018,8 +1018,8 @@ - - + + @@ -2155,9 +2155,9 @@ - + - + @@ -2210,21 +2210,21 @@ java.lang.Object - + - + - + - + - + @@ -2240,8 +2240,8 @@ - - + + @@ -3622,9 +3622,9 @@ - + - + @@ -3677,21 +3677,21 @@ java.lang.Object - + - + - + - + - + @@ -3707,8 +3707,8 @@ - - + + @@ -4916,9 +4916,9 @@ - + - + @@ -4971,21 +4971,21 @@ java.lang.Object - + - + - + - + - + @@ -5001,8 +5001,8 @@ - - + + @@ -6198,9 +6198,9 @@ - + - + @@ -6253,21 +6253,21 @@ java.lang.Object - + - + - + - + - + @@ -6283,8 +6283,8 @@ - - + + @@ -7793,9 +7793,9 @@ - + - + @@ -7848,21 +7848,21 @@ java.lang.Object - + - + - + - + - + @@ -7878,8 +7878,8 @@ - - + + @@ -9526,9 +9526,9 @@ - + - + @@ -9581,21 +9581,21 @@ java.lang.Object - + - + - + - + - + @@ -9611,8 +9611,8 @@ - - + + @@ -11451,9 +11451,9 @@ - + - + @@ -11506,21 +11506,21 @@ java.lang.Object - + - + - + - + - + @@ -11536,8 +11536,8 @@ - - + + @@ -12859,9 +12859,9 @@ - + - + @@ -12914,21 +12914,21 @@ java.lang.Object - + - + - + - + - + @@ -12944,8 +12944,8 @@ - - + + @@ -14251,9 +14251,9 @@ - + - + @@ -14306,21 +14306,21 @@ java.lang.Object - + - + - + - + - + @@ -14336,8 +14336,8 @@ - - + + @@ -15665,9 +15665,9 @@ - + - + @@ -15720,21 +15720,21 @@ java.lang.Object - + - + - + - + - + @@ -15750,8 +15750,8 @@ - - + + @@ -17104,9 +17104,9 @@ - + - + @@ -17159,21 +17159,21 @@ java.lang.Object - + - + - + - + - + @@ -17189,8 +17189,8 @@ - - + + @@ -18606,9 +18606,9 @@ - + - + @@ -18661,21 +18661,21 @@ java.lang.Object - + - + - + - + - + @@ -18691,8 +18691,8 @@ - - + + @@ -20268,9 +20268,9 @@ - + - + @@ -20323,21 +20323,21 @@ java.lang.Object - + - + - + - + - + @@ -20353,8 +20353,8 @@ - - + + @@ -21859,9 +21859,9 @@ - + - + @@ -21914,21 +21914,21 @@ java.lang.Object - + - + - + - + - + @@ -21944,8 +21944,8 @@ - - + + @@ -23161,9 +23161,9 @@ - + - + @@ -23216,21 +23216,21 @@ java.lang.Object - + - + - + - + - + @@ -23246,8 +23246,8 @@ - - + + @@ -24463,9 +24463,9 @@ - + - + @@ -24518,21 +24518,21 @@ java.lang.Object - + - + - + - + - + @@ -24548,8 +24548,8 @@ - - + + @@ -28581,9 +28581,9 @@ - + - + @@ -28636,21 +28636,21 @@ java.lang.Object - + - + - + - + - + @@ -28666,8 +28666,8 @@ - - + + @@ -30014,9 +30014,9 @@ - + - + @@ -30069,21 +30069,21 @@ java.lang.Object - + - + - + - + - + @@ -30099,8 +30099,8 @@ - - + + @@ -31679,9 +31679,9 @@ - + - + @@ -31734,21 +31734,21 @@ java.lang.Object - + - + - + - + - + @@ -31764,8 +31764,8 @@ - - + + @@ -33348,9 +33348,9 @@ - + - + @@ -33403,21 +33403,21 @@ java.lang.Object - + - + - + - + - + @@ -33433,8 +33433,8 @@ - - + + @@ -38739,9 +38739,9 @@ - + - + @@ -38794,21 +38794,21 @@ java.lang.Object - + - + - + - + - + @@ -38824,8 +38824,8 @@ - - + + @@ -41662,9 +41662,9 @@ - + - + @@ -41717,21 +41717,21 @@ java.lang.Object - + - + - + - + - + @@ -41747,8 +41747,8 @@ - - + + @@ -42959,9 +42959,9 @@ - + - + @@ -43014,21 +43014,21 @@ java.lang.Object - + - + - + - + - + @@ -43044,8 +43044,8 @@ - - + + @@ -44345,9 +44345,9 @@ - + - + @@ -44400,21 +44400,21 @@ java.lang.Object - + - + - + - + - + @@ -44430,8 +44430,8 @@ - - + + @@ -46069,9 +46069,9 @@ - + - + @@ -46124,21 +46124,21 @@ java.lang.Object - + - + - + - + - + @@ -46154,8 +46154,8 @@ - - + + @@ -47715,9 +47715,9 @@ - + - + @@ -47770,21 +47770,21 @@ java.lang.Object - + - + - + - + - + @@ -47800,8 +47800,8 @@ - - + + @@ -49361,9 +49361,9 @@ - + - + @@ -49416,21 +49416,21 @@ java.lang.Object - + - + - + - + - + @@ -49446,8 +49446,8 @@ - - + + @@ -51053,9 +51053,9 @@ - + - + @@ -51108,21 +51108,21 @@ java.lang.Object - + - + - + - + - + @@ -51138,8 +51138,8 @@ - - + + @@ -52511,9 +52511,9 @@ - + - + @@ -52566,21 +52566,21 @@ java.lang.Object - + - + - + - + - + @@ -52596,8 +52596,8 @@ - - + + From cfd662208b2ecbe56fdbb32a7c084671f10c155a Mon Sep 17 00:00:00 2001 From: andi-huber Date: Tue, 12 May 2026 09:25:38 +0200 Subject: [PATCH 10/18] CAUSEWAY-3997: refining MetaModelService; no wildcard support for Object_patchColumnOrder --- .../columnorder/Object_patchColumnOrder.java | 46 ++- .../services/metamodel/MetaModelService.java | 39 +-- .../metamodel/MetaModelServiceDefault.java | 85 +++-- ...sViewer_IntegTest.dump_facets.approved.xml | 12 +- ...nDomain_IntegTest.dump_facets.approved.xml | 12 +- ...sViewer_IntegTest.dump_facets.approved.xml | 12 +- ...pDomain_IntegTest.dump_facets.approved.xml | 12 +- ...etaModelRegressionTest.verify.approved.xml | 300 ++++-------------- 8 files changed, 179 insertions(+), 339 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java index 2d52fbf8228..52acfdd243c 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -24,6 +24,8 @@ import jakarta.inject.Inject; +import org.jspecify.annotations.Nullable; + import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.Action; import org.apache.causeway.applib.annotation.ActionLayout; @@ -31,7 +33,6 @@ import org.apache.causeway.applib.annotation.Introspection; import org.apache.causeway.applib.annotation.MemberSupport; import org.apache.causeway.applib.annotation.Nature; -import org.apache.causeway.applib.annotation.Optionality; import org.apache.causeway.applib.annotation.Parameter; import org.apache.causeway.applib.annotation.ParameterLayout; import org.apache.causeway.applib.annotation.PrecedingParamsPolicy; @@ -42,6 +43,8 @@ import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureRepository; import org.apache.causeway.applib.services.metamodel.MetaModelService; +import org.apache.causeway.applib.services.metamodel.MetaModelService.AssociationsLookup; +import org.apache.causeway.commons.internal.exceptions._Exceptions; import lombok.RequiredArgsConstructor; @@ -80,15 +83,15 @@ public static class ActionDomainEvent @MemberSupport public Object act( - @Parameter(optionality = Optionality.OPTIONAL) + @Parameter @ParameterLayout(describedAs = "The 'Feature', for which the patch is to be applied (in-memory), " + "that is, " + "either for a one-to-many relation (a PARENTED Collection), " - + "or a domain-type (a STANDALONE Collection). " + + "or a domain-type (applies to all STANDALONE Collections of that element-type). " + "The Feature either represents a particular one-to-many relation of this domain-type or " + "represents the domain-type itself or one of the domain-type's super types. " - + "If 'none', patches all one-to-many relations of this domain-type (acting as a wildcard). " - + "Wildcard patches have least priority.") + + "The Apache Causeway Programming Model also supports {parent-type, element-type} scoped " + + "column order definitions, which are not covered by patching yet.") final ApplicationFeatureId featureId, @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.RESET) @@ -110,21 +113,40 @@ public static class ActionDomainEvent .toList(); } - @MemberSupport public String defaultColumnDefinition(final ApplicationFeatureId collectionId) { - var memberId = applicationFeatureRepository.asIdentifier(collectionId) - .orElse(null); // not found or none selected (yet) + @MemberSupport public String defaultColumnDefinition(final @Nullable ApplicationFeatureId appFeatureId) { + if(appFeatureId==null) + return "# no feature selected"; + + var featureId = applicationFeatureRepository.asIdentifier(appFeatureId) + .orElseThrow(); // not found -> unexpected + + if(featureId.type().isCollection()) + return listing( + metaModelService.parentedAssociationsForColumnRendering(mixee, featureId, AssociationsLookup.AVAILABLE), + metaModelService.parentedAssociationsForColumnRendering(mixee, featureId, AssociationsLookup.ENABLED)); + + if(featureId.type().isClass()) + return listing( + metaModelService.standaloneAssociationsForColumnRendering(featureId.logicalType(), AssociationsLookup.AVAILABLE), + metaModelService.standaloneAssociationsForColumnRendering(featureId.logicalType(), AssociationsLookup.ENABLED)); + + throw _Exceptions.illegalArgument("unsupported feature type %s", featureId.type()); + } + + // -- HELPER + private String listing(final Stream availableIds, final Stream enabledIds) { // all column candidates - var available = metaModelService.streamAvailableAssociationsForColumnRendering(mixee.getClass(), memberId) + var available = availableIds .map(Identifier::memberLogicalName) .collect(Collectors.joining("\n")); - // all columns currently configured - var enabled = metaModelService.streamEnabledAssociationsForColumnRendering(mixee.getClass(), memberId, mixee) + // all columns currently rendered + var enabled = enabledIds .map(Identifier::memberLogicalName) .collect(Collectors.joining("\n")); - // TODO flesh out using Listing + // TODO flesh out using Listing? return "# available\n" + available + "\n\n# enabled\n" diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java index 943d521f776..08663a889f7 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java @@ -209,36 +209,39 @@ enum Mode { Stream streamTypeHierarchy(@Nullable Class domainType); /** - * Stream of {@link Identifier} representing the Columns of given domainType and Member of interest. + * Parameter to lookup associations for column rendering. * - *

Where columns returned are those that are in principle available. Also those are returned in no particular order. + * @since 4.0 + */ + public enum AssociationsLookup { + AVAILABLE, + ENABLED + } + + /** + * Stream of {@link Identifier} representing the Columns for specified PARENTED collection. + * + *

Columns returned are those that are either in principle AVAILABLE or currently ENABLED, based on given {@link AssociationsLookup}. * - *

If domainType is null returns an empty {@link Stream}. + *

The availability lookup returns {@link Identifier}(s) in no particular order, + * whereas the enablement lookup returns them in same order as rendered. * - *

Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType. + *

If parentDomainObject is null returns an empty {@link Stream}. * - *

FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods) - *

FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here * @since 4.0 */ - Stream streamAvailableAssociationsForColumnRendering(@Nullable Class domainType, @Nullable Identifier memberIdentifier); + Stream parentedAssociationsForColumnRendering(Object parentDomainObject, Identifier collectionId, AssociationsLookup lookup); /** - * Stream of {@link Identifier} representing the Columns of given domainType and Member of interest. - * - *

Where columns returned are those that are currently visible. Also those are returned in same order as rendered. + * Stream of {@link Identifier} representing the Columns for specified STANDALONE collection. * - *

If domainType is null returns an empty {@link Stream}. + *

Columns returned are those that are either in principle AVAILABLE or currently ENABLED, based on given {@link AssociationsLookup}. * - *

Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType. + *

The availability lookup returns {@link Identifier}(s) in no particular order, + * whereas the enablement lookup returns them in same order as rendered. * - *

FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods) - *

FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here * @since 4.0 */ - Stream streamEnabledAssociationsForColumnRendering( - @Nullable Class domainType, - @Nullable Identifier memberIdentifier, - @Nullable Object domainObject); + Stream standaloneAssociationsForColumnRendering(LogicalType logicalType, AssociationsLookup lookup); } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java index 5158eed0cb1..e538d826e73 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java @@ -24,7 +24,6 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.BiPredicate; -import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -53,13 +52,13 @@ import org.apache.causeway.applib.services.metamodel.MetaModelService; import org.apache.causeway.applib.services.metamodel.objgraph.ObjectGraph; import org.apache.causeway.commons.collections.Can; -import org.apache.causeway.commons.functional.Either; import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.collections._Lists; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; -import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.core.metamodel.facets.members.publish.command.CommandPublishingFacet; +import org.apache.causeway.core.metamodel.object.ManagedObject; +import org.apache.causeway.core.metamodel.object.ManagedObjects; import org.apache.causeway.core.metamodel.services.metamodel.MetaModelAnnotator.ExporterConfig; import org.apache.causeway.core.metamodel.spec.ActionScope; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; @@ -303,65 +302,61 @@ public Stream streamTypeHierarchy(@Nullable final Class domainTyp .specForType(domainType) .stream() .flatMap(ObjectSpecification::streamTypeHierarchyAndInterfaces) - .filter(spec->spec.getCorrespondingClass().equals(Object.class)) + .filter(spec->!spec.getCorrespondingClass().equals(Object.class)) .map(ObjectSpecification::getFeatureIdentifier); } @Override - public Stream streamAvailableAssociationsForColumnRendering( - @Nullable final Class domainType, - @Nullable final Identifier memberIdentifier) { + public Stream parentedAssociationsForColumnRendering( + final Object parentDomainObject, + final Identifier collectionId, + final AssociationsLookup lookup) { - var elementType = lookupColumnHolder(domainType, memberIdentifier) - .map(either->either.fold(OneToManyAssociation::getElementType, UnaryOperator.identity())) - .orElse(null); - - if(elementType==null) + if(parentDomainObject==null + || collectionId==null) return Stream.empty(); - return elementType.streamAssociations(MixedIn.INCLUDED) - .map(ObjectAssociation::getFeatureIdentifier); - } - - @Override - public Stream streamEnabledAssociationsForColumnRendering( - @Nullable final Class domainType, - @Nullable final Identifier memberIdentifier, - @Nullable final Object domainObject) { - - var elementType = lookupColumnHolder(domainType, memberIdentifier) - .map(either->either.fold(OneToManyAssociation::getElementType, UnaryOperator.identity())) - .orElse(null); + var parentMo = ManagedObject.adaptSingular(specificationLoader(), parentDomainObject); + if(ManagedObjects.isNullOrUnspecifiedOrEmpty(parentMo)) + return Stream.empty(); + var elementType = parentMo.objSpec().getCollection(collectionId.memberLogicalName()) + .map(OneToManyAssociation::getElementType) + .orElse(null); if(elementType==null) return Stream.empty(); - var mo = MetaModelContext.instanceElseFail() - .getObjectManager() - .adapt(domainObject); - - return elementType.streamAssociationsForColumnRendering(memberIdentifier, mo) - .map(ObjectAssociation::getFeatureIdentifier); + return switch (lookup) { + case AVAILABLE -> elementType + .streamAssociations(MixedIn.INCLUDED) + .map(ObjectAssociation::getFeatureIdentifier); + case ENABLED -> parentMo.objSpec() + .streamAssociationsForColumnRendering(collectionId, parentMo) + .map(ObjectAssociation::getFeatureIdentifier); + }; } - // -- HELPER - - private Optional> lookupColumnHolder( - @Nullable final Class domainType, - @Nullable final Identifier memberIdentifier) { + @Override + public Stream standaloneAssociationsForColumnRendering( + final LogicalType logicalType, + final AssociationsLookup lookup) { - var domObjSpec = specificationLoader() - .specForType(domainType) + var elementType = specificationLoader() + .specForLogicalType(logicalType) .orElse(null); - if(domObjSpec == null) - return Optional.empty(); - - if(memberIdentifier == null) - return Optional.of(Either.right(domObjSpec)); + if(elementType == null) + return Stream.empty(); - return domObjSpec.getCollection(memberIdentifier.memberLogicalName()) - .map(Either::left); + return switch (lookup) { + case AVAILABLE -> elementType + .streamAssociations(MixedIn.INCLUDED) + .map(ObjectAssociation::getFeatureIdentifier); + case ENABLED -> elementType + //TODO room for API improvement + .streamAssociationsForColumnRendering(null, null) + .map(ObjectAssociation::getFeatureIdentifier); + }; } } diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml index d076d082842..60bd015b252 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml @@ -1141,9 +1141,9 @@ - + - + @@ -1157,11 +1157,11 @@ - - + + - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml index 6a6c8081842..cfa8406b634 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml @@ -1134,9 +1134,9 @@ - + - + @@ -1150,11 +1150,11 @@ - - + + - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml index ca386fe7fab..bee5da58f66 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml @@ -1130,9 +1130,9 @@ - + - + @@ -1146,11 +1146,11 @@ - - + + - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml index a7325dc7e69..b52df45b557 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml @@ -1123,9 +1123,9 @@ - + - + @@ -1139,11 +1139,11 @@ - - + + - - + + diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index 7592b451f15..2b67dedfcbb 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -993,9 +993,9 @@ - + - + @@ -1009,12 +1009,6 @@ - - - - - - @@ -2215,9 +2209,9 @@ - + - + @@ -2231,12 +2225,6 @@ - - - - - - @@ -3682,9 +3670,9 @@ - + - + @@ -3698,12 +3686,6 @@ - - - - - - @@ -4976,9 +4958,9 @@ - + - + @@ -4992,12 +4974,6 @@ - - - - - - @@ -6258,9 +6234,9 @@ - + - + @@ -6274,12 +6250,6 @@ - - - - - - @@ -7853,9 +7823,9 @@ - + - + @@ -7869,12 +7839,6 @@ - - - - - - @@ -9586,9 +9550,9 @@ - + - + @@ -9602,12 +9566,6 @@ - - - - - - @@ -11511,9 +11469,9 @@ - + - + @@ -11527,12 +11485,6 @@ - - - - - - @@ -12919,9 +12871,9 @@ - + - + @@ -12935,12 +12887,6 @@ - - - - - - @@ -14311,9 +14257,9 @@ - + - + @@ -14327,12 +14273,6 @@ - - - - - - @@ -15725,9 +15665,9 @@ - + - + @@ -15741,12 +15681,6 @@ - - - - - - @@ -17164,9 +17098,9 @@ - + - + @@ -17180,12 +17114,6 @@ - - - - - - @@ -18666,9 +18594,9 @@ - + - + @@ -18682,12 +18610,6 @@ - - - - - - @@ -20328,9 +20250,9 @@ - + - + @@ -20344,12 +20266,6 @@ - - - - - - @@ -21919,9 +21835,9 @@ - + - + @@ -21935,12 +21851,6 @@ - - - - - - @@ -23221,9 +23131,9 @@ - + - + @@ -23237,12 +23147,6 @@ - - - - - - @@ -24523,9 +24427,9 @@ - + - + @@ -24539,12 +24443,6 @@ - - - - - - @@ -28641,9 +28539,9 @@ - + - + @@ -28657,12 +28555,6 @@ - - - - - - @@ -30074,9 +29966,9 @@ - + - + @@ -30090,12 +29982,6 @@ - - - - - - @@ -31739,9 +31625,9 @@ - + - + @@ -31755,12 +31641,6 @@ - - - - - - @@ -33408,9 +33288,9 @@ - + - + @@ -33424,12 +33304,6 @@ - - - - - - @@ -38799,9 +38673,9 @@ - + - + @@ -38815,12 +38689,6 @@ - - - - - - @@ -41722,9 +41590,9 @@ - + - + @@ -41738,12 +41606,6 @@ - - - - - - @@ -43019,9 +42881,9 @@ - + - + @@ -43035,12 +42897,6 @@ - - - - - - @@ -44405,9 +44261,9 @@ - + - + @@ -44421,12 +44277,6 @@ - - - - - - @@ -46129,9 +45979,9 @@ - + - + @@ -46145,12 +45995,6 @@ - - - - - - @@ -47775,9 +47619,9 @@ - + - + @@ -47791,12 +47635,6 @@ - - - - - - @@ -49421,9 +49259,9 @@ - + - + @@ -49437,12 +49275,6 @@ - - - - - - @@ -51113,9 +50945,9 @@ - + - + @@ -51129,12 +50961,6 @@ - - - - - - @@ -52571,9 +52397,9 @@ - + - + @@ -52587,12 +52413,6 @@ - - - - - - From e743bac8444f73c36468911dc0fe81356631bfda Mon Sep 17 00:00:00 2001 From: andi-huber Date: Tue, 12 May 2026 09:50:30 +0200 Subject: [PATCH 11/18] CAUSEWAY-3997: prepares quick-icons for AppFeatIds --- .../applib/services/appfeat/ApplicationFeatureId.java | 8 ++++++++ .../applib/services/appfeat/ApplicationFeatureSort.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java index 421ffbcd11e..86560388314 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java @@ -33,6 +33,7 @@ import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.applib.annotation.Programmatic; import org.apache.causeway.applib.annotation.Value; +import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.id.LogicalType; import org.apache.causeway.applib.util.Equality; import org.apache.causeway.applib.util.Hashing; @@ -218,6 +219,13 @@ private ApplicationFeatureId(final ApplicationFeatureSort sort) { return buf.toString(); } + // -- ICON + + //FIXME not supported yet - perhaps use value semantics instead? + @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconSize iconSize) { + return new ObjectSupport.FontAwesomeIconResource(FontAwesomeLayers.fromQuickNotation(sort.quickIcon())); + } + // -- PROPERTIES @Getter final @NonNull ApplicationFeatureSort sort; diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java index 2b9e0c4521d..464d23c51cb 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java @@ -65,4 +65,12 @@ public String toString() { return name(); } + public String quickIcon() { + return switch (this) { + case NAMESPACE -> "solid sitemap"; + case MEMBER -> "solid circle-plus .col-indigo"; + case TYPE -> "solid circle .col-indigo"; + }; + } + } From 2f8e09ec3bdde7bdf3639bfdfbd9a0404addab51 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Thu, 14 May 2026 09:37:11 +0200 Subject: [PATCH 12/18] Revert "CAUSEWAY-3997: prepares quick-icons for AppFeatIds" This reverts commit e941dff83f68fa38f906ce56d932ca556c544e7d. --- .../applib/services/appfeat/ApplicationFeatureId.java | 8 -------- .../applib/services/appfeat/ApplicationFeatureSort.java | 8 -------- 2 files changed, 16 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java index 86560388314..421ffbcd11e 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java @@ -33,7 +33,6 @@ import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.applib.annotation.Programmatic; import org.apache.causeway.applib.annotation.Value; -import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.id.LogicalType; import org.apache.causeway.applib.util.Equality; import org.apache.causeway.applib.util.Hashing; @@ -219,13 +218,6 @@ private ApplicationFeatureId(final ApplicationFeatureSort sort) { return buf.toString(); } - // -- ICON - - //FIXME not supported yet - perhaps use value semantics instead? - @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconSize iconSize) { - return new ObjectSupport.FontAwesomeIconResource(FontAwesomeLayers.fromQuickNotation(sort.quickIcon())); - } - // -- PROPERTIES @Getter final @NonNull ApplicationFeatureSort sort; diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java index 464d23c51cb..2b9e0c4521d 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureSort.java @@ -65,12 +65,4 @@ public String toString() { return name(); } - public String quickIcon() { - return switch (this) { - case NAMESPACE -> "solid sitemap"; - case MEMBER -> "solid circle-plus .col-indigo"; - case TYPE -> "solid circle .col-indigo"; - }; - } - } From d844d02afb904b64003d70af4db4332edc303b60 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Thu, 14 May 2026 09:41:06 +0200 Subject: [PATCH 13/18] CAUSEWAY-3997: ApplicationFeatureIdValueSemantics CSS fix --- .../valuesemantics/ApplicationFeatureIdValueSemantics.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java index f500c398fff..766b499235f 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java @@ -81,8 +81,8 @@ public String htmlPresentation(final ValueSemanticsProvider.Context context, fin private String iconify(final ApplicationFeatureId featureId) { var fa = switch (featureId.getSort()) { case NAMESPACE -> "fa-solid fa-sitemap"; - case MEMBER -> "fa-solid fa-circle-plus .col-indigo"; - case TYPE -> "fa-solid fa-circle .col-indigo"; + case MEMBER -> "fa-solid fa-circle-plus col-indigo"; + case TYPE -> "fa-solid fa-circle col-indigo"; }; return faIconAndTitle(fa, toMonospace(featureId.stringify())); From 62902c28acc2a99da98fc59311f9a602028821d5 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Fri, 15 May 2026 10:30:03 +0200 Subject: [PATCH 14/18] CAUSEWAY-3997: ApplicationFeatureIdValueSemantics better icons --- .../semantics/ValueSemanticsAbstract.java | 36 ++-- .../ApplicationFeatureIdValueSemantics.java | 13 +- .../model/css/causeway-supplemental.css | 177 +++++++++++++++--- 3 files changed, 179 insertions(+), 47 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java b/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java index 70902a46fec..f718556dfdf 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java @@ -43,6 +43,7 @@ import org.apache.causeway.applib.annotation.TimePrecision; import org.apache.causeway.applib.exceptions.recoverable.TextEntryParseException; +import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.locale.UserLocale; import org.apache.causeway.applib.services.bookmark.IdStringifier; import org.apache.causeway.applib.services.i18n.TranslationContext; @@ -227,8 +228,9 @@ protected Optional parseInteger( final ValueSemanticsProvider.@Nullable Context context, final @Nullable String text) { var input = _Strings.blankToNullOrTrim(text); - if(input==null) - return Optional.empty(); + if(input==null) { + return Optional.empty(); + } try { return parseDecimal(context, input, GroupingSeparatorPolicy.ALLOW) .map(BigDecimal::toBigIntegerExact); @@ -248,15 +250,17 @@ protected Optional parseDecimal( final @Nullable String text, final GroupingSeparatorPolicy groupingSeparatorPolicy) { var input = _Strings.blankToNullOrTrim(text); - if(input==null) - return Optional.empty(); + if(input==null) { + return Optional.empty(); + } if (groupingSeparatorPolicy == GroupingSeparatorPolicy.DISALLOW) { var userLocale = getUserLocale(context); var decimalFormatSymbols = new DecimalFormatSymbols(userLocale.numberFormatLocale()); var groupingSeparatorChar = decimalFormatSymbols.getGroupingSeparator(); - if (input.contains(""+groupingSeparatorChar)) - throw new TextEntryParseException("Invalid value '" + input + "'; do not use the '" + groupingSeparatorChar + "' grouping separator"); + if (input.contains(""+groupingSeparatorChar)) { + throw new TextEntryParseException("Invalid value '" + input + "'; do not use the '" + groupingSeparatorChar + "' grouping separator"); + } } var format = getNumberFormat(context, FormatUsageFor.PARSING); @@ -265,17 +269,19 @@ protected Optional parseDecimal( var position = new ParsePosition(0); try { var number = (BigDecimal)format.parse(input, position); - if (position.getErrorIndex() != -1) - throw new ParseException("could not parse input='" + input + "'", position.getErrorIndex()); - else if (position.getIndex() < input.length()) - throw new ParseException("input='" + input + "' was not processed completely", position.getIndex()); + if (position.getErrorIndex() != -1) { + throw new ParseException("could not parse input='" + input + "'", position.getErrorIndex()); + } else if (position.getIndex() < input.length()) { + throw new ParseException("input='" + input + "' was not processed completely", position.getIndex()); + } // check for maxFractionDigits if required ... final int maxFractionDigits = format.getMaximumFractionDigits(); if(maxFractionDigits>-1 - && number.scale()>format.getMaximumFractionDigits()) - throw new TextEntryParseException(String.format( + && number.scale()>format.getMaximumFractionDigits()) { + throw new TextEntryParseException(String.format( "No more than %d digits can be entered after the decimal separator, " + "got %d in '%s'.", maxFractionDigits, number.scale(), input)); + } return Optional.of(number); } catch (final NumberFormatException | ParseException e) { throw new TextEntryParseException(String.format( @@ -402,9 +408,11 @@ protected final String toMonospace(final String html) { /** * Uses Fontawesome. */ - protected final String faIconAndTitle(final String faClasses, final String titleHtml) { + protected final String faIconAndTitle(final FontAwesomeLayers faLayers, final String titleHtml) { return """ - %s""".formatted(faClasses, titleHtml); + %s%s""".formatted(faLayers.toHtml(), titleHtml); } + + } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java index 766b499235f..b49a7c4c865 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/ApplicationFeatureIdValueSemantics.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Component; import org.apache.causeway.applib.annotation.PriorityPrecedence; +import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; import org.apache.causeway.applib.value.semantics.Parser; import org.apache.causeway.applib.value.semantics.Renderer; @@ -79,13 +80,13 @@ public String htmlPresentation(final ValueSemanticsProvider.Context context, fin // uses font-awesome private String iconify(final ApplicationFeatureId featureId) { - var fa = switch (featureId.getSort()) { - case NAMESPACE -> "fa-solid fa-sitemap"; - case MEMBER -> "fa-solid fa-circle-plus col-indigo"; - case TYPE -> "fa-solid fa-circle col-indigo"; + var faQuickNotation = switch (featureId.getSort()) { + case NAMESPACE -> "solid border-all .col-chocolate"; // resembling the package symbol from Eclipse IDE + case TYPE -> "solid copyright .col-green"; // resembling the class symbol from Eclipse IDE + case MEMBER -> "solid cube .col-gold"; // resembling member symbol as used in Eclipse Theia }; - - return faIconAndTitle(fa, toMonospace(featureId.stringify())); + var faLayers = FontAwesomeLayers.fromQuickNotation(faQuickNotation); + return faIconAndTitle(faLayers, toMonospace(featureId.stringify())); } // -- PARSER diff --git a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/css/causeway-supplemental.css b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/css/causeway-supplemental.css index 02724d0e45f..b2a0ad9a5b7 100644 --- a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/css/causeway-supplemental.css +++ b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/css/causeway-supplemental.css @@ -58,30 +58,153 @@ i.ov-bottom-85 { top: 0.85em; } -i.col-black { - color: black; -} -i.col-grey { - color: grey; -} -i.col-green { - color: green; -} -i.col-red { - color: red; -} -i.col-orange { - color: orange; -} -i.col-blue { - color: blue; -} -i.col-dodgerblue { - color: dodgerblue; -} -i.col-gold { - color: gold; -} -i.col-indigo { - color: indigo; -} +/* named colors */ + +i.col-aliceblue { color: aliceblue; } +i.col-antiquewhite { color: antiquewhite; } +i.col-aqua { color: aqua; } +i.col-aquamarine { color: aquamarine; } +i.col-azure { color: azure; } +i.col-beige { color: beige; } +i.col-bisque { color: bisque; } +i.col-black { color: black; } +i.col-blanchedalmond { color: blanchedalmond; } +i.col-blue { color: blue; } +i.col-blueviolet { color: blueviolet; } +i.col-brown { color: brown; } +i.col-burlywood { color: burlywood; } +i.col-cadetblue { color: cadetblue; } +i.col-chartreuse { color: chartreuse; } +i.col-chocolate { color: chocolate; } +i.col-coral { color: coral; } +i.col-cornflowerblue { color: cornflowerblue; } +i.col-cornsilk { color: cornsilk; } +i.col-crimson { color: crimson; } +i.col-cyan { color: cyan; } +i.col-darkblue { color: darkblue; } +i.col-darkcyan { color: darkcyan; } +i.col-darkgoldenrod { color: darkgoldenrod; } +i.col-darkgray { color: darkgray; } +i.col-darkgreen { color: darkgreen; } +i.col-darkgrey { color: darkgrey; } +i.col-darkkhaki { color: darkkhaki; } +i.col-darkmagenta { color: darkmagenta; } +i.col-darkolivegreen { color: darkolivegreen; } +i.col-darkorange { color: darkorange; } +i.col-darkorchid { color: darkorchid; } +i.col-darkred { color: darkred; } +i.col-darksalmon { color: darksalmon; } +i.col-darkseagreen { color: darkseagreen; } +i.col-darkslateblue { color: darkslateblue; } +i.col-darkslategray { color: darkslategray; } +i.col-darkslategrey { color: darkslategrey; } +i.col-darkturquoise { color: darkturquoise; } +i.col-darkviolet { color: darkviolet; } +i.col-deeppink { color: deeppink; } +i.col-deepskyblue { color: deepskyblue; } +i.col-dimgray { color: dimgray; } +i.col-dimgrey { color: dimgrey; } +i.col-dodgerblue { color: dodgerblue; } +i.col-firebrick { color: firebrick; } +i.col-floralwhite { color: floralwhite; } +i.col-forestgreen { color: forestgreen; } +i.col-fuchsia { color: fuchsia; } +i.col-gainsboro { color: gainsboro; } +i.col-ghostwhite { color: ghostwhite; } +i.col-gold { color: gold; } +i.col-goldenrod { color: goldenrod; } +i.col-gray { color: gray; } +i.col-green { color: green; } +i.col-greenyellow { color: greenyellow; } +i.col-grey { color: grey; } +i.col-honeydew { color: honeydew; } +i.col-hotpink { color: hotpink; } +i.col-indianred { color: indianred; } +i.col-indigo { color: indigo; } +i.col-ivory { color: ivory; } +i.col-khaki { color: khaki; } +i.col-lavender { color: lavender; } +i.col-lavenderblush { color: lavenderblush; } +i.col-lawngreen { color: lawngreen; } +i.col-lemonchiffon { color: lemonchiffon; } +i.col-lightblue { color: lightblue; } +i.col-lightcoral { color: lightcoral; } +i.col-lightcyan { color: lightcyan; } +i.col-lightgoldenrodyellow { color: lightgoldenrodyellow; } +i.col-lightgray { color: lightgray; } +i.col-lightgreen { color: lightgreen; } +i.col-lightgrey { color: lightgrey; } +i.col-lightpink { color: lightpink; } +i.col-lightsalmon { color: lightsalmon; } +i.col-lightseagreen { color: lightseagreen; } +i.col-lightskyblue { color: lightskyblue; } +i.col-lightslategray { color: lightslategray; } +i.col-lightslategrey { color: lightslategrey; } +i.col-lightsteelblue { color: lightsteelblue; } +i.col-lightyellow { color: lightyellow; } +i.col-lime { color: lime; } +i.col-limegreen { color: limegreen; } +i.col-linen { color: linen; } +i.col-magenta { color: magenta; } +i.col-maroon { color: maroon; } +i.col-mediumaquamarine { color: mediumaquamarine; } +i.col-mediumblue { color: mediumblue; } +i.col-mediumorchid { color: mediumorchid; } +i.col-mediumpurple { color: mediumpurple; } +i.col-mediumseagreen { color: mediumseagreen; } +i.col-mediumslateblue { color: mediumslateblue; } +i.col-mediumspringgreen { color: mediumspringgreen; } +i.col-mediumturquoise { color: mediumturquoise; } +i.col-mediumvioletred { color: mediumvioletred; } +i.col-midnightblue { color: midnightblue; } +i.col-mintcream { color: mintcream; } +i.col-mistyrose { color: mistyrose; } +i.col-moccasin { color: moccasin; } +i.col-navajowhite { color: navajowhite; } +i.col-navy { color: navy; } +i.col-oldlace { color: oldlace; } +i.col-olive { color: olive; } +i.col-olivedrab { color: olivedrab; } +i.col-orange { color: orange; } +i.col-orangered { color: orangered; } +i.col-orchid { color: orchid; } +i.col-palegoldenrod { color: palegoldenrod; } +i.col-palegreen { color: palegreen; } +i.col-paleturquoise { color: paleturquoise; } +i.col-palevioletred { color: palevioletred; } +i.col-papayawhip { color: papayawhip; } +i.col-peachpuff { color: peachpuff; } +i.col-peru { color: peru; } +i.col-pink { color: pink; } +i.col-plum { color: plum; } +i.col-powderblue { color: powderblue; } +i.col-purple { color: purple; } +i.col-rebeccapurple { color: rebeccapurple; } +i.col-red { color: red; } +i.col-rosybrown { color: rosybrown; } +i.col-royalblue { color: royalblue; } +i.col-saddlebrown { color: saddlebrown; } +i.col-salmon { color: salmon; } +i.col-sandybrown { color: sandybrown; } +i.col-seagreen { color: seagreen; } +i.col-seashell { color: seashell; } +i.col-sienna { color: sienna; } +i.col-silver { color: silver; } +i.col-skyblue { color: skyblue; } +i.col-slateblue { color: slateblue; } +i.col-slategray { color: slategray; } +i.col-slategrey { color: slategrey; } +i.col-snow { color: snow; } +i.col-springgreen { color: springgreen; } +i.col-steelblue { color: steelblue; } +i.col-tan { color: tan; } +i.col-teal { color: teal; } +i.col-thistle { color: thistle; } +i.col-tomato { color: tomato; } +i.col-turquoise { color: turquoise; } +i.col-violet { color: violet; } +i.col-wheat { color: wheat; } +i.col-white { color: white; } +i.col-whitesmoke { color: whitesmoke; } +i.col-yellow { color: yellow; } +i.col-yellowgreen { color: yellowgreen; } From 8e911eebbfd16c41267aec498150aa7f7ad7f61c Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 16 May 2026 09:51:25 +0200 Subject: [PATCH 15/18] CAUSEWAY-3997: find a place to hold column order patches - fixes TableColumnOrderServiceUsingTxtFile service order not honored - query modes for columns AVAILABE/ENABLED --- .../services/metamodel/MetaModelService.java | 14 +- .../CausewayModuleCoreMetamodel.java | 7 +- .../columnorder/ColumnOrderPatchingFacet.java | 85 ++++++++++ .../metamodel/MetaModelServiceDefault.java | 60 +++---- .../TableColumnOrderServiceDefault.java | 77 --------- .../TableColumnOrderServiceUsingTxtFile.java | 2 +- .../spec/ObjectSpecificationRecord.java | 5 +- .../feature/ObjectAssociationContainer.java | 39 ++++- .../spec/impl/ObjectSpecificationDefault.java | 160 ++++++++---------- .../spec/impl/_MembersAsColumns.java | 155 ++++++++++------- .../tabular/internal/DataTableInternal.java | 23 ++- .../core/metamodel/util/WhereContexts.java | 41 ----- .../CollectionContentsAsAjaxTablePanel.java | 15 +- 13 files changed, 352 insertions(+), 331 deletions(-) create mode 100644 core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java delete mode 100644 core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceDefault.java delete mode 100644 core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/WhereContexts.java diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java index 08663a889f7..ff96d891951 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java @@ -214,8 +214,18 @@ enum Mode { * @since 4.0 */ public enum AssociationsLookup { - AVAILABLE, - ENABLED + /** + * The Column Order SPI and Column Order Patching have potential to hide otherwise visible columns. + * This query mode shows all columns, that are not permanently hidden while ignoring hiding from above mechanisms. + */ + AVAILABLE, + /** + * This query mode shows all currently visible columns. + */ + ENABLED; + + public boolean isAvailable() { return this==AVAILABLE; } + public boolean isEnabled() { return this==ENABLED; } } /** diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java index f867582cee4..85229a362d5 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java @@ -27,6 +27,7 @@ import org.jspecify.annotations.NonNull; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; @@ -71,7 +72,6 @@ import org.apache.causeway.core.metamodel.services.layout.LayoutServiceDefault; import org.apache.causeway.core.metamodel.services.metamodel.MetaModelServiceDefault; import org.apache.causeway.core.metamodel.services.registry.ServiceRegistryDefault; -import org.apache.causeway.core.metamodel.services.tablecol.TableColumnOrderServiceDefault; import org.apache.causeway.core.metamodel.services.tablecol.TableColumnOrderServiceUsingTxtFile; import org.apache.causeway.core.metamodel.services.title.TitleServiceDefault; import org.apache.causeway.core.metamodel.spec.impl.CausewayModuleCoreMetamodelConfigurationDefault; @@ -194,8 +194,7 @@ MetaModelServiceDefault.class, ServiceInjectorDefault.class, ServiceRegistryDefault.class, - TableColumnOrderServiceDefault.class, - TableColumnOrderServiceUsingTxtFile.class, + // TableColumnOrderServiceUsingTxtFile.class, NOT here ... use @ComponentScan instead, see below TitleServiceDefault.class, // @Repository's @@ -210,6 +209,8 @@ //last MetamodelInitializer.class }) +// in order for Spring to honor service precedence, services need to be autodetected +@ComponentScan(basePackageClasses = { TableColumnOrderServiceUsingTxtFile.class }) public class CausewayModuleCoreMetamodel { public static final String NAMESPACE = "causeway.metamodel"; diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java new file mode 100644 index 00000000000..518c30665e4 --- /dev/null +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.causeway.core.metamodel.facets.collections.layout.columnorder; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import org.jspecify.annotations.Nullable; + +import org.apache.causeway.applib.Identifier; +import org.apache.causeway.commons.collections.Can; +import org.apache.causeway.core.metamodel.facetapi.Facet; +import org.apache.causeway.core.metamodel.facetapi.FacetHolder; + +/** + * Holder of a {@link Map} that is mutable during runtime and collects column order patching information. + * + *

To be installed on collection's element types. + * + * @apiNote Only used when PROTOTYPING. + * + * @since 4.0 + */ +public record ColumnOrderPatchingFacet( + FacetHolder facetHolder, + Map> columnOrder) implements Facet { + + public ColumnOrderPatchingFacet(final FacetHolder facetHolder) { + this(facetHolder, new ConcurrentHashMap<>()); + } + + @Override + public Class facetType() { + return ColumnOrderPatchingFacet.class; + } + + @Override + public Precedence precedence() { + return Precedence.DEFAULT; + } + + /** + * Clears the map entry. + * @param identifier if null, acts as a no-op + */ + public void clearColumnOrder(final @Nullable Identifier identifier) { + columnOrder.remove(identifier); + } + + /** + * @param identifier if null, acts as a no-op + * @param columnListing if null, clears the map entry + */ + public void putColumnOrder(final @Nullable Identifier identifier, @Nullable final Iterable columnListing) { + if(columnListing==null) { + clearColumnOrder(identifier); + return; + } + columnOrder.put(identifier, Can.ofIterable(columnListing)); + } + + public Optional> lookupColumnOrder(final @Nullable Identifier identifier) { + return identifier!=null + ? Optional.ofNullable(columnOrder.get(identifier)) + : Optional.empty(); + } + +} diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java index e538d826e73..35e23603f51 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java @@ -65,6 +65,7 @@ import org.apache.causeway.core.metamodel.spec.feature.MixedIn; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation; +import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociationContainer.ColumnQuery; import org.apache.causeway.core.metamodel.spec.feature.ObjectMember; import org.apache.causeway.core.metamodel.spec.feature.OneToManyAssociation; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; @@ -179,14 +180,14 @@ protected boolean isBuiltIn(final ObjectSpecification spec) { public BeanSort sortOf( final @Nullable Class domainType, final Mode mode) { if(domainType == null) - return null; + return null; final ObjectSpecification objectSpec = specificationLoader().specForType(domainType).orElse(null); if(objectSpec == null) - return BeanSort.UNKNOWN; + return BeanSort.UNKNOWN; if(objectSpec.getBeanSort().isUnknown() && !(mode == Mode.RELAXED)) - throw new IllegalArgumentException(String.format( + throw new IllegalArgumentException(String.format( "Unable to determine what sort of domain object this is: '%s'. Originating domainType: '%s'", objectSpec.getFullIdentifier(), domainType.getName() @@ -199,7 +200,7 @@ public BeanSort sortOf( @Override public BeanSort sortOf(final Bookmark bookmark, final Mode mode) { if(bookmark == null) - return null; + return null; final Class domainType = switch (mode) { case RELAXED -> specificationLoader().specForBookmark(bookmark) @@ -226,18 +227,18 @@ public CommandDtoProcessor commandDtoProcessorFor(final String memberIdentifier) final String logicalTypeName = featureId.getLogicalTypeName(); if(_Strings.isNullOrEmpty(logicalTypeName)) - return null; + return null; final ObjectSpecification spec = specificationLoader().specForLogicalTypeName(logicalTypeName).orElse(null); if(spec == null) - return null; + return null; final ObjectMember objectMemberIfAny = spec.getMember(featureId.getLogicalMemberName()).orElse(null); if (objectMemberIfAny == null) - return null; + return null; final CommandPublishingFacet commandPublishingFacet = objectMemberIfAny.lookupFacet(CommandPublishingFacet.class) .orElse(null); if(commandPublishingFacet == null) - return null; + return null; return commandPublishingFacet.getProcessor(); } @@ -310,53 +311,42 @@ public Stream streamTypeHierarchy(@Nullable final Class domainTyp public Stream parentedAssociationsForColumnRendering( final Object parentDomainObject, final Identifier collectionId, - final AssociationsLookup lookup) { + final AssociationsLookup columnQueryMode) { if(parentDomainObject==null || collectionId==null) - return Stream.empty(); + return Stream.empty(); var parentMo = ManagedObject.adaptSingular(specificationLoader(), parentDomainObject); if(ManagedObjects.isNullOrUnspecifiedOrEmpty(parentMo)) - return Stream.empty(); + return Stream.empty(); var elementType = parentMo.objSpec().getCollection(collectionId.memberLogicalName()) .map(OneToManyAssociation::getElementType) .orElse(null); if(elementType==null) - return Stream.empty(); - - return switch (lookup) { - case AVAILABLE -> elementType - .streamAssociations(MixedIn.INCLUDED) - .map(ObjectAssociation::getFeatureIdentifier); - case ENABLED -> parentMo.objSpec() - .streamAssociationsForColumnRendering(collectionId, parentMo) - .map(ObjectAssociation::getFeatureIdentifier); - }; + return Stream.empty(); + + return elementType + .streamAssociationsForColumnRendering(new ColumnQuery(collectionId, parentMo, columnQueryMode)) + .map(ObjectAssociation::getFeatureIdentifier); } @Override public Stream standaloneAssociationsForColumnRendering( final LogicalType logicalType, - final AssociationsLookup lookup) { + final AssociationsLookup columnQueryMode) { var elementType = specificationLoader() - .specForLogicalType(logicalType) - .orElse(null); + .specForLogicalType(logicalType) + .orElse(null); if(elementType == null) - return Stream.empty(); - - return switch (lookup) { - case AVAILABLE -> elementType - .streamAssociations(MixedIn.INCLUDED) - .map(ObjectAssociation::getFeatureIdentifier); - case ENABLED -> elementType - //TODO room for API improvement - .streamAssociationsForColumnRendering(null, null) - .map(ObjectAssociation::getFeatureIdentifier); - }; + return Stream.empty(); + + return elementType + .streamAssociationsForColumnRendering(ColumnQuery.forStandaloneTable(columnQueryMode)) + .map(ObjectAssociation::getFeatureIdentifier); } } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceDefault.java deleted file mode 100644 index c6acec9ad04..00000000000 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceDefault.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.causeway.core.metamodel.services.tablecol; - -import java.util.List; - -import jakarta.annotation.Priority; -import jakarta.inject.Named; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Service; - -import org.apache.causeway.applib.annotation.PriorityPrecedence; -import org.apache.causeway.applib.services.tablecol.TableColumnOrderService; -import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; - -/** - * The default implementation of {@link TableColumnOrderService}. - * Note though that this implementation has lower priority (later precedence) than - * {@link TableColumnOrderServiceUsingTxtFile}. - * - * @since 1.x {@index} - * - * @see TableColumnOrderServiceUsingTxtFile - */ -@Service -@Named(CausewayModuleCoreMetamodel.NAMESPACE + ".TableColumnOrderServiceDefault") -@Priority(PriorityPrecedence.LATE) -@Qualifier("Default") -public class TableColumnOrderServiceDefault implements TableColumnOrderService { - - /** - * Just returns the propertyIds unchanged. - * - * @param parent - * @param collectionId - * @param elementType - * @param associationIds - */ - @Override - public List orderParented( - final Object parent, - final String collectionId, - final Class elementType, - final List associationIds) { - return associationIds; - } - - /** - * Just returns the propertyIds unchanged. - * - * @param domainType - * @param associationIds - */ - @Override - public List orderStandalone( - final Class domainType, - final List associationIds) { - return associationIds; - } -} diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java index 30d2fb47aca..dc493a419b4 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/tablecol/TableColumnOrderServiceUsingTxtFile.java @@ -90,7 +90,7 @@ */ @Service @Named(CausewayModuleCoreMetamodel.NAMESPACE + ".TableColumnOrderServiceUsingTxtFile") -@Priority(PriorityPrecedence.LATE - 100) // before Default +@Priority(PriorityPrecedence.LATE - 100) @Qualifier("UsingFiles") @Slf4j public class TableColumnOrderServiceUsingTxtFile implements TableColumnOrderService { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java index fd6e163ee81..aa0d162f85c 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java @@ -30,7 +30,6 @@ import org.apache.causeway.core.metamodel.facetapi.FacetHolder; import org.apache.causeway.core.metamodel.facetapi.FeatureType; import org.apache.causeway.core.metamodel.facetapi.HasFacetHolder; -import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.feature.MixedIn; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; import org.apache.causeway.core.metamodel.spec.feature.ObjectActionContainer; @@ -107,8 +106,8 @@ public record ObjectSpecificationRecord( @Override public Stream streamAssociations(final MixedIn mixedIn) { return associationContainer.streamAssociations(mixedIn); } - @Override public Stream streamAssociationsForColumnRendering(final Identifier memberIdentifier, final ManagedObject parentObject) { - return associationContainer.streamAssociationsForColumnRendering(memberIdentifier, parentObject); + @Override public Stream streamAssociationsForColumnRendering(final ColumnQuery columnQuery) { + return associationContainer.streamAssociationsForColumnRendering(columnQuery); } @Override public Stream streamDeclaredAssociations(final MixedIn mixedIn) { return associationContainer.streamDeclaredAssociations(mixedIn); diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java index 2424377dfcf..e518fb2508b 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java @@ -21,8 +21,13 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.apache.causeway.applib.Identifier; +import org.apache.causeway.applib.annotation.Where; +import org.apache.causeway.applib.services.metamodel.MetaModelService.AssociationsLookup; import org.apache.causeway.commons.internal.exceptions._Exceptions; +import org.apache.causeway.core.metamodel.interactions.managed.ManagedMember; import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.ObjectSpecificationException; @@ -130,13 +135,41 @@ default Stream streamCollections(final MixedIn mixedIn){ .map(OneToManyAssociation.class::cast); } + + /** + * Properties and Collections visible as columns, honoring order and visibility. + * @param parentObject not used for standalone tables and allowed to be empty for parented ones + */ + record ColumnQuery( + @Nullable Identifier memberIdentifier, + @Nullable ManagedObject parentObject, + AssociationsLookup mode) { + public static ColumnQuery forStandaloneTable(final AssociationsLookup mode) { + return new ColumnQuery(null, null, mode); + } + public ColumnQuery(final ManagedMember managedMember, final AssociationsLookup enabled) { + this(managedMember.getIdentifier(), managedMember.getOwner(), enabled); + } + /** + * The collection variant (standalone or parented). + */ + public Where where() { + return isStandalone() + ? Where.STANDALONE_TABLES + : Where.PARENTED_TABLES; + } + public boolean isStandalone() { + return memberIdentifier==null + || memberIdentifier.type().isClass() + || memberIdentifier.type().isAction(); + } + } + /** * Properties and Collections visible as columns, honoring order and visibility. * @param parentObject not used for standalone tables and allowed to be empty for parented ones */ - Stream streamAssociationsForColumnRendering( - Identifier memberIdentifier, - ManagedObject parentObject); + Stream streamAssociationsForColumnRendering(ColumnQuery columnQuery); // -- ASSOCIATION STREAMS (INHERITANCE NOT CONSIDERED) diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java index 635d7310d68..f751a94eb08 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java @@ -132,6 +132,7 @@ final class ObjectSpecificationDefault private final FacetedMethodsBuilder facetedMethodsBuilder; private final ClassSubstitutorRegistry classSubstitutorRegistry; + private final _MembersAsColumns columnHelper; @Getter(onMethod_={@Override}) private final IntrospectionPolicy introspectionPolicy; @@ -167,11 +168,13 @@ public ObjectSpecificationDefault( // naturally supports attribute inheritance from the type's hierarchy this.introspectionPolicy = this.lookupFacet(IntrospectionPolicyFacet.class) - .map(introspectionPolicyFacet->introspectionPolicyFacet.getIntrospectionPolicy()) + .map(IntrospectionPolicyFacet::getIntrospectionPolicy) .orElseGet(()->mmc.getConfiguration().core().metaModel().introspector().policy()); this.facetedMethodsBuilder = new FacetedMethodsBuilder(this, facetProcessor, classSubstitutorRegistry); + + this.columnHelper = new _MembersAsColumns(mmc); } // -- CONTRACT @@ -250,13 +253,12 @@ private Stream createAssociations() { } private ObjectAssociation createAssociation(final FacetedMethod facetMethod) { - if (facetMethod.featureType().isCollection()) { - return OneToManyAssociationDefault.forMethod(facetMethod); - } else if (facetMethod.featureType().isProperty()) { - return OneToOneAssociationDefault.forMethod(facetMethod); - } else { - return null; - } + if (facetMethod.featureType().isCollection()) + return OneToManyAssociationDefault.forMethod(facetMethod); + else if (facetMethod.featureType().isProperty()) + return OneToOneAssociationDefault.forMethod(facetMethod); + else + return null; } private Stream createActions() { @@ -279,9 +281,8 @@ private ObjectAction createAction(final FacetedMethod facetedMethod) { return this.isMixin() ? ObjectActionDefault.forMixinMain(facetedMethod) : ObjectActionDefault.forMethod(facetedMethod); - } else { - return null; - } + } else + return null; } // -- getObjectAction @@ -352,7 +353,7 @@ private void catalogueActions(final BiConsumer onM private final _Lazy> elementSpecification = _Lazy.threadSafe(()->lookupFacet(TypeOfFacet.class) - .map(typeOfFacet -> typeOfFacet.elementSpec())); + .map(TypeOfFacet::elementSpec)); @Override public Optional explicitElementSpec() { @@ -361,19 +362,14 @@ public Optional explicitElementSpec() { // -- TABLE COLUMN RENDERING - @Override - public final Stream streamAssociationsForColumnRendering( - final Identifier memberIdentifier, - final ManagedObject parentObject) { - return new _MembersAsColumns(getMetaModelContext()) - .streamAssociationsForColumnRendering(this, memberIdentifier, parentObject); + @Override public Stream streamAssociationsForColumnRendering(final ColumnQuery columnQuery) { + return columnHelper.streamAssociationsForColumnRendering(this, columnQuery); } @Override public Stream streamActionsForColumnRendering( final Identifier memberIdentifier) { - return new _MembersAsColumns(getMetaModelContext()) - .streamActionsForColumnRendering(this, memberIdentifier); + return columnHelper.streamActionsForColumnRendering(this, memberIdentifier); } // -- DETERMINE INJECTABILITY @@ -505,7 +501,7 @@ public final String getFullIdentifier() { } @Override - public void introspect(IntrospectionRequest request) { + public void introspect(final IntrospectionRequest request) { switch (request) { case REGISTER -> introspectUpTo(IntrospectionState.NOT_INTROSPECTED, ()->"introspect(%s)".formatted(request)); @@ -542,8 +538,9 @@ enum IntrospectionState { /** * @param introspectionContextProvider keeps track of the causal chain of introspection requests */ - private void introspectUpTo(final IntrospectionState upTo, Supplier introspectionContextProvider) { - if(!isLessThan(upTo)) return; // optimization + private void introspectUpTo(final IntrospectionState upTo, final Supplier introspectionContextProvider) { + if(!isLessThan(upTo)) + return; // optimization if(log.isDebugEnabled()) { log.debug("introspectingUpTo: {}, {}", getFullIdentifier(), upTo); @@ -596,21 +593,21 @@ private boolean isLessThan(final IntrospectionState upTo) { protected void loadSpecOfSuperclass(final Class superclass) { if (superclass == null) - return; - + return; + this.superclassSpec = specLoaderInternal().loadSpecification(superclass); - if (superclassSpec != null + if (superclassSpec != null && log.isDebugEnabled()) { log.debug(" Superclass {}", superclass.getName()); } } - + protected void loadSpecOfInterfaces(final Class[] interfaces) { if(interfaces==null) - return; - + return; + var classCache = _ClassCache.getInstance(); - + final List interfaceSpecList = Stream.of(interfaces) // pre-filter common interfaces (performance) .filter(interfaceType->!interfaceType.getName().startsWith("java.")) @@ -628,23 +625,23 @@ protected void loadSpecOfInterfaces(final Class[] interfaces) { .map(specLoaderInternal()::loadSpecification) .filter(Objects::nonNull) .toList(); - + if(!interfaceSpecList.isEmpty()) { if(interfaceSpecList.size()>1) { ValidationFailure.raiseFormatted(facetHolder, - "Cannot use @DomainObject on more than one interface, as inherited by: %s", - getCorrespondingClass().getName()); + "Cannot use @DomainObject on more than one interface, as inherited by: %s", + getCorrespondingClass().getName()); } if (superclassSpec != null) { var superType = superclassSpec.getCorrespondingClass(); if(classCache.head(superType).hasAnnotation(DomainObject.class)) { ValidationFailure.raiseFormatted(facetHolder, - "Cannot use @DomainObject on both, abstract super class and one interface, as inherited by: %s", + "Cannot use @DomainObject on both, abstract super class and one interface, as inherited by: %s", getCorrespondingClass().getName()); } } - -//debug + +//debug // System.err.println("%s".formatted(getCorrespondingClass().getName())); // interfaceSpecList.forEach(i->{ // System.err.println("- %s".formatted(i.getCorrespondingClass().getName())); @@ -653,7 +650,7 @@ protected void loadSpecOfInterfaces(final Class[] interfaces) { this.interfaces.clear(); this.interfaces.addAll(interfaceSpecList); unmodifiableInterfaces.clear(); - } + } } } @@ -747,7 +744,8 @@ public String getTitle(final TitleRenderRequest titleRenderRequest) { private void notifySubscribersIfEntity( final TitleRenderRequest titleRenderRequest, final String titleString) { - if (!isEntity()) return; + if (!isEntity()) + return; var managedObject = titleRenderRequest.object(); managedObject.getBookmark().ifPresent(bookmark -> { @@ -779,7 +777,7 @@ public Can getAliases() { // -- ICON @Override - public Optional getIcon(final ManagedObject domainObject, ObjectSupport.IconSize iconSize) { + public Optional getIcon(final ManagedObject domainObject, final ObjectSupport.IconSize iconSize) { if(ManagedObjects.isSpecified(domainObject)) { _Assert.assertEquals(domainObject.objSpec(), this); } @@ -825,7 +823,7 @@ public boolean isOfTypeResolvePrimitive(final ObjectSpecification other) { @Override public String getSingularName() { return lookupFacet(ObjectNamedFacet.class) - .flatMap(textFacet->textFacet.translated()) + .flatMap(ObjectNamedFacet::translated) // unexpected code reach, however keep for JUnit testing .orElseGet(()->String.format( "(%s has neither title- nor object-named-facet)", @@ -896,12 +894,10 @@ private static class NotANoopFacetFilter implements Predicate getMember(final String memberId) { introspectUpTo(IntrospectionState.FULLY_INTROSPECTED, ()->"getMember %s of %s".formatted(memberId, this.getFeatureIdentifier())); - if(_Strings.isEmpty(memberId)) return Optional.empty(); + if(_Strings.isEmpty(memberId)) + return Optional.empty(); var objectAction = getAction(memberId); - if(objectAction.isPresent()) return objectAction; + if(objectAction.isPresent()) + return objectAction; var association = getAssociation(memberId); - if(association.isPresent()) return association; + if(association.isPresent()) + return association; return Optional.empty(); } @@ -967,7 +966,8 @@ public Optional getDeclaredAssociation(final String id, final introspectUpTo(IntrospectionState.FULLY_INTROSPECTED, ()->"getDeclaredAssociation %s of %s".formatted(id, this.getFeatureIdentifier())); - if(_Strings.isEmpty(id)) return Optional.empty(); + if(_Strings.isEmpty(id)) + return Optional.empty(); return streamDeclaredAssociations(mixedIn) .filter(objectAssociation->objectAssociation.getId().equals(id)) @@ -999,9 +999,8 @@ public Stream streamDeclaredActions( * Creates all mixed in properties and collections for this spec. */ private Stream createMixedInAssociations() { - if (isInjectable() || isValue()) { - return Stream.empty(); - } + if (isInjectable() || isValue()) + return Stream.empty(); return getCausewayBeanTypeRegistry().streamMixinTypes() .flatMap(this::createMixedInAssociation); } @@ -1010,17 +1009,14 @@ private Stream createMixedInAssociation(final Class mixinT var mixinSpec = specLoaderInternal().loadSpecification(mixinType, IntrospectionRequest.FULL); if (mixinSpec == null - || mixinSpec == this) { - return Stream.empty(); - } + || mixinSpec == this) + return Stream.empty(); var mixinFacet = mixinSpec.mixinFacet().orElse(null); - if(mixinFacet == null) { - // this shouldn't happen; to be covered by meta-model validation later - return Stream.empty(); - } - if(!mixinFacet.isMixinFor(getCorrespondingClass())) { + if(mixinFacet == null) + // this shouldn't happen; to be covered by meta-model validation later return Stream.empty(); - } + if(!mixinFacet.isMixinFor(getCorrespondingClass())) + return Stream.empty(); var mixinMethodName = mixinFacet.getMainMethodName(); return mixinSpec.streamActions(ActionScope.ANY, MixedIn.EXCLUDED) @@ -1044,22 +1040,18 @@ private Stream createMixedInAction(final Class mixinType var mixinSpec = specLoaderInternal().loadSpecification(mixinType, IntrospectionRequest.FULL); if (mixinSpec == null - || mixinSpec == this) { - return Stream.empty(); - } + || mixinSpec == this) + return Stream.empty(); var mixinFacet = mixinSpec.mixinFacet().orElse(null); - if(mixinFacet == null) { - // this shouldn't happen; to be covered by meta-model validation later - return Stream.empty(); - } - if(!mixinFacet.isMixinFor(getCorrespondingClass())) { + if(mixinFacet == null) + // this shouldn't happen; to be covered by meta-model validation later return Stream.empty(); - } + if(!mixinFacet.isMixinFor(getCorrespondingClass())) + return Stream.empty(); // don't mixin Object_ mixins to domain services if(getBeanSort().isManagedBeanContributing() - && mixinFacet.isMixinFor(java.lang.Object.class)) { - return Stream.empty(); - } + && mixinFacet.isMixinFor(java.lang.Object.class)) + return Stream.empty(); var mixinMethodName = mixinFacet.getMainMethodName(); @@ -1143,14 +1135,12 @@ private void createMixedInActionsAndResort() { || getBeanSort().isManagedBeanContributing() // in support of composite value-type constructor mixins || getBeanSort().isValue(); - if(!include) { - return; - } + if(!include) + return; var mixedInActions = createMixedInActions() .collect(Collectors.toList()); - if(mixedInActions.isEmpty()) { - return; // nothing to do (this spec has no mixed-in actions, regular actions have already been added) - } + if(mixedInActions.isEmpty()) + return; // nothing to do (this spec has no mixed-in actions, regular actions have already been added) var regularActions = _Lists.newArrayList(objectActions); // defensive copy @@ -1166,14 +1156,12 @@ private void createMixedInActionsAndResort() { * one-shot: must be no-op, if already created */ private void createMixedInAssociationsAndResort() { - if(!isEntityOrViewModelOrAbstract()) { - return; - } + if(!isEntityOrViewModelOrAbstract()) + return; var mixedInAssociations = createMixedInAssociations() .collect(Collectors.toList()); - if(mixedInAssociations.isEmpty()) { - return; // nothing to do (this spec has no mixed-in associations, regular associations have already been added) - } + if(mixedInAssociations.isEmpty()) + return; // nothing to do (this spec has no mixed-in associations, regular associations have already been added) var regularAssociations = _Lists.newArrayList(associations); // defensive copy diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java index 8967665f058..5547157e68c 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java @@ -24,41 +24,45 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.Where; import org.apache.causeway.applib.layout.component.PropertyLayoutData; import org.apache.causeway.applib.services.tablecol.TableColumnOrderService; import org.apache.causeway.applib.services.tablecol.TableColumnVisibilityService; -import org.apache.causeway.commons.internal.base._NullSafe; -import org.apache.causeway.core.metamodel.context.HasMetaModelContext; +import org.apache.causeway.commons.collections.Can; +import org.apache.causeway.commons.internal.functions._Predicates; import org.apache.causeway.core.metamodel.context.MetaModelContext; +import org.apache.causeway.core.metamodel.facets.collections.layout.columnorder.ColumnOrderPatchingFacet; import org.apache.causeway.core.metamodel.facets.object.grid.GridFacet; -import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.MixedIn; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation; -import org.apache.causeway.core.metamodel.util.WhereContexts; +import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociationContainer.ColumnQuery; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +record _MembersAsColumns( + boolean isColumnOrderPatchingEnabled, + Can tableColumnVisibilityServices, + Can tableColumnOrderServices) { -@RequiredArgsConstructor -class _MembersAsColumns implements HasMetaModelContext { + _MembersAsColumns(final MetaModelContext mmc) { + this( + mmc.getSystemEnvironment().isPrototyping(), + mmc.getServiceRegistry().select(TableColumnVisibilityService.class), + mmc.getServiceRegistry().select(TableColumnOrderService.class)); + } - @Getter(onMethod_ = {@Override}) - private final MetaModelContext metaModelContext; - - public final Stream streamActionsForColumnRendering( + public Stream streamActionsForColumnRendering( final ObjectSpecification elementType, final Identifier memberIdentifier) { - if(elementType.isValue()) return Stream.empty(); + if(elementType.isValue()) + return Stream.empty(); return elementType.streamRuntimeActions(MixedIn.INCLUDED) .filter(ObjectAction.Predicates.visibleAccordingToHiddenFacet(Where.STANDALONE_TABLES)) @@ -68,56 +72,58 @@ public final Stream streamActionsForColumnRendering( /** * @param parentObject not used for standalone tables and allowed to be empty for parented ones */ - public final Stream streamAssociationsForColumnRendering( - // the type that has the properties and collections that make up this table's columns - final ObjectSpecification elementType, - // if null corresponds to a standalone table - final @Nullable Identifier memberIdentifier, - final ManagedObject parentObject) { - - var assocById = new LinkedHashMap(); - final Where whereContext; - - if(memberIdentifier==null) { // handle the standalone case - whereContext = Where.STANDALONE_TABLES; - elementType.streamAssociations(MixedIn.INCLUDED) - .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(whereContext)) - .filter(assoc->filterColumnsUsingSpi(assoc, elementType.getCorrespondingClass())) // optional SPI to filter columns; - .forEach(assoc->assocById.put(assoc.getId(), assoc)); - - } else { - whereContext = WhereContexts.collectionVariant(memberIdentifier); - elementType.streamAssociations(MixedIn.INCLUDED) - .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(whereContext)) - .filter(ObjectAssociation.Predicates.referencesParent(parentObject.objSpec()).negate()) - .filter(assoc->filterColumnsUsingSpi(assoc, elementType.getCorrespondingClass())) // optional SPI to filter columns; - .forEach(assoc->assocById.put(assoc.getId(), assoc)); - } + public Stream streamAssociationsForColumnRendering( + // the type that has the properties and collections that make up this table's columns + final ObjectSpecification elementType, + final ColumnQuery columnQuery) { + var assocById = assembleAvailableColumns(elementType, columnQuery); var assocIdsInOrder = new ArrayList<>(assocById.keySet()); // sort by order of occurrence within associated layout, if any propertyIdComparator(elementType) .ifPresent(assocIdsInOrder::sort); - // optional SPI to reorder columns - sortColumnsUsingSpi(whereContext, memberIdentifier, parentObject, assocIdsInOrder, elementType.getCorrespondingClass()); + // when querying for AVAILABLE columns, we skip the column sorting SPI and also the column-patching (PROTOTYPING feature) + if(columnQuery.mode().isEnabled()) { + if(!sortColumnsUsingPatch(columnQuery, assocIdsInOrder, elementType)) { + // SPI to reorder columns, where TableColumnOrderServiceUsingTxtFile is a built-in one + // apply only, if not patched + sortColumnsUsingSpi(columnQuery, assocIdsInOrder, elementType); + } + } - // add all ordered columns to the table + // stream columns in final order return assocIdsInOrder.stream() .map(assocById::get) - .filter(_NullSafe::isPresent); + .filter(Objects::nonNull); } // -- HELPER - private boolean filterColumnsUsingSpi( + private Map assembleAvailableColumns( + final ObjectSpecification elementType, + final ColumnQuery columnQuery) { + + final var assocById = new LinkedHashMap(); + + elementType.streamAssociations(MixedIn.INCLUDED) + .filter(ObjectAssociation.Predicates.visibleAccordingToHiddenFacet(columnQuery.where())) + .filter(columnQuery.isStandalone() + ? _Predicates.alwaysTrue() + : ObjectAssociation.Predicates.referencesParent(columnQuery.parentObject().objSpec()).negate()) + .filter(assoc->hideColumnUsingSpi(assoc, elementType.getCorrespondingClass())) + .forEach(assoc->assocById.put(assoc.getId(), assoc)); + + return assocById; + } + + private boolean hideColumnUsingSpi( final ObjectAssociation assoc, final Class elementType) { - return getServiceRegistry() - .select(TableColumnVisibilityService.class) + return tableColumnVisibilityServices .stream() - .noneMatch(x -> x.hides(elementType, assoc.getId())); + .noneMatch(it -> it.hides(elementType, assoc.getId())); } // comparator based on grid facet, that is by order of occurrence within associated layout @@ -129,14 +135,16 @@ private Optional> propertyIdComparator( // one object of that type has been rendered via DomainObjectPage. var elementTypeGridFacet = elementTypeSpec.lookupFacet(GridFacet.class).orElse(null); - if(elementTypeGridFacet == null) return Optional.empty(); + if(elementTypeGridFacet == null) + return Optional.empty(); // the facet should always exist, in fact // just enough to ask for the metadata. // don't pass in any object, just need the meta-data var elementTypeGrid = elementTypeGridFacet.getGrid(null); - if(elementTypeGrid ==null) return Optional.empty(); + if(elementTypeGrid ==null) + return Optional.empty(); final Map propertyIdOrderWithinGrid = new HashMap<>(); elementTypeGrid.streamPropertyLayoutData() @@ -155,30 +163,53 @@ private Optional> propertyIdComparator( .thenComparing(Comparator.naturalOrder())); } + + /** + * @return whether a column-order patch was found and applied + */ + private boolean sortColumnsUsingPatch( + final ColumnQuery columnQuery, + final List assocIdsInOrder, + final ObjectSpecification elementType) { + + if(!isColumnOrderPatchingEnabled) + return false; + + var identifier = columnQuery.isStandalone() + ? elementType.getFeatureIdentifier() + : columnQuery.memberIdentifier(); + Objects.requireNonNull(identifier, ()->"framework bug"); + + var patchedColumnOrder = elementType + .lookupFacet(ColumnOrderPatchingFacet.class) + .flatMap(it->it.lookupColumnOrder(columnQuery.memberIdentifier())) + .orElse(null); + if(patchedColumnOrder==null) + return false; + + return true; + } + private void sortColumnsUsingSpi( - final Where whereContext, - final @Nullable Identifier memberIdentifier, - // not used for standalone tables, and allowed to be empty in parented ones - final ManagedObject parentObject, + final ColumnQuery columnQuery, final List assocIdsInOrder, - final Class elementType) { + final ObjectSpecification elementType) { - var tableColumnOrderServices = getServiceRegistry().select(TableColumnOrderService.class); if(tableColumnOrderServices.isEmpty()) - return; + return; tableColumnOrderServices.stream() .map(tableColumnOrderService-> - whereContext.inStandaloneTable() + columnQuery.isStandalone() ? tableColumnOrderService.orderStandalone( - elementType, + elementType.getCorrespondingClass(), assocIdsInOrder) : tableColumnOrderService.orderParented( - parentObject.getPojo(), - memberIdentifier.memberLogicalName(), - elementType, + columnQuery.parentObject().getPojo(), + columnQuery.memberIdentifier().memberLogicalName(), + elementType.getCorrespondingClass(), assocIdsInOrder)) - .filter(_NullSafe::isPresent) + .filter(Objects::nonNull) .findFirst() .filter(assocReorderedIds->assocReorderedIds!=assocIdsInOrder) // skip if its the same object .ifPresent(assocReorderedIds->{ diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/internal/DataTableInternal.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/internal/DataTableInternal.java index ac4e2564da0..71d88ddd937 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/internal/DataTableInternal.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/internal/DataTableInternal.java @@ -33,6 +33,7 @@ import org.apache.causeway.applib.annotation.TableDecorator; import org.apache.causeway.applib.annotation.Where; import org.apache.causeway.applib.services.filter.CollectionFilterService; +import org.apache.causeway.applib.services.metamodel.MetaModelService.AssociationsLookup; import org.apache.causeway.commons.binding.Bindable; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.functional.IndexedFunction; @@ -50,6 +51,7 @@ import org.apache.causeway.core.metamodel.object.ManagedObjects; import org.apache.causeway.core.metamodel.object.PackedManagedObject; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; +import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociationContainer.ColumnQuery; import org.apache.causeway.core.metamodel.spec.feature.ObjectMember; import org.apache.causeway.core.metamodel.tabular.DataColumn; import org.apache.causeway.core.metamodel.tabular.DataRow; @@ -87,9 +89,8 @@ public static DataTableInternal forAction( if(actionResult==null) { new DataTableInternal(managedAction, managedAction.getWhere(), Can.empty()); } - if(!(actionResult instanceof PackedManagedObject)) { - throw _Exceptions.unexpectedCodeReach(); - } + if(!(actionResult instanceof PackedManagedObject)) + throw _Exceptions.unexpectedCodeReach(); var elements = ((PackedManagedObject)actionResult).unpack(); elements.forEach(ManagedObject::getBookmark); @@ -179,7 +180,7 @@ private DataTableInternal( this.dataColumnsObservable = _Observables.lazy(()-> managedMember.getElementType() - .streamAssociationsForColumnRendering(managedMember.getIdentifier(), managedMember.getOwner()) + .streamAssociationsForColumnRendering(new ColumnQuery(managedMember, AssociationsLookup.ENABLED)) .map(assoc->new DataColumnInternal(this, assoc)) .collect(Can.toCan())); @@ -359,18 +360,16 @@ public Set getSelectedRowIndexes() { public ActionInteraction startAssociatedActionInteraction(final String actionId, final Where where) { if(managedMember.getOwner().specialization().isEmpty() - || managedMember.getOwner().getEntityState().isTransientOrRemoved()) { - throw _Exceptions.illegalArgument("cannot start action interaction on missing or deleted action owner"); - } + || managedMember.getOwner().getEntityState().isTransientOrRemoved()) + throw _Exceptions.illegalArgument("cannot start action interaction on missing or deleted action owner"); var featureId = managedMember.getIdentifier(); - if(!featureId.type().isPropertyOrCollection()) { - return ActionInteraction.empty(String.format("[no such collection %s; instead got %s;" + if(!featureId.type().isPropertyOrCollection()) + return ActionInteraction.empty(String.format("[no such collection %s; instead got %s;" + "(while searching for an associated action %s)]", featureId, featureId.type(), actionId)); - } return ActionInteraction.startWithMultiselect(managedMember.getOwner(), actionId, where, this); } @@ -385,7 +384,7 @@ public DataTable export() { .map(DataColumn::associationMetaModel), dataRowsFilteredAndSortedObservable().getValue() .stream() - .map(dr->dr.rowElement()) + .map(DataRow::rowElement) .collect(Can.toCan())); } @@ -484,7 +483,7 @@ public void setupBindings(final DataTableInteractive tableInteractive) { this.selectedRowIndexes = tableInteractive.getSelectedRowIndexes(); }); } - + @Override public String toString() { return "Memento[featureId=%s,dataTable.rowCount=%d]".formatted( diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/WhereContexts.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/WhereContexts.java deleted file mode 100644 index 42b7fb7b96b..00000000000 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/WhereContexts.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.causeway.core.metamodel.util; - -import org.apache.causeway.applib.Identifier; -import org.apache.causeway.applib.annotation.Where; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class WhereContexts { - - /** - * Utility, that detects the collection variant (standalone or parented), - * based on the feature {@link Identifier} of the originating feature, - * that is, the feature is either a plural member or a plural action result. - */ - public Where collectionVariant(final Identifier featureId) { - var whereContext = featureId.type().isAction() - ? Where.STANDALONE_TABLES - : Where.PARENTED_TABLES; - return whereContext; - } - -} diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/present/ajaxtable/CollectionContentsAsAjaxTablePanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/present/ajaxtable/CollectionContentsAsAjaxTablePanel.java index 8c91965679a..3cebd649d92 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/present/ajaxtable/CollectionContentsAsAjaxTablePanel.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/present/ajaxtable/CollectionContentsAsAjaxTablePanel.java @@ -29,10 +29,11 @@ import org.apache.wicket.model.Model; import org.apache.wicket.request.resource.CssResourceReference; +import org.apache.causeway.applib.services.metamodel.MetaModelService.AssociationsLookup; import org.apache.causeway.core.config.CausewayConfiguration.Viewer.Wicket; -import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation; +import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociationContainer.ColumnQuery; import org.apache.causeway.core.metamodel.spec.feature.OneToManyAssociation; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; import org.apache.causeway.core.metamodel.tabular.DataTableInteractive; @@ -193,13 +194,15 @@ private void addPropertyColumnsIfRequired( var collectionModel = getModel(); var elementType = collectionModel.getElementType(); - if(elementType == null) return; - - final ManagedObject parentObject = collectionModel.getParentObject(); - var memberIdentifier = collectionModel.getIdentifier(); + if(elementType == null) + return; // add all ordered columns to the table - elementType.streamAssociationsForColumnRendering(memberIdentifier, parentObject) + elementType + .streamAssociationsForColumnRendering(new ColumnQuery( + collectionModel.getIdentifier(), + collectionModel.getParentObject(), + AssociationsLookup.ENABLED)) .map(ObjectAssociation::getSpecialization) .map(spez->spez.fold( this::createSingularColumn, From 1bd392f940a27c1260bb631a6c97679669339390 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 16 May 2026 10:12:37 +0200 Subject: [PATCH 16/18] CAUSEWAY-3997: logic to apply patches --- .../layout/columnorder/ColumnOrderPatchingFacet.java | 2 ++ .../spec/feature/ObjectAssociationContainer.java | 6 +++++- .../core/metamodel/spec/impl/_MembersAsColumns.java | 12 ++++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java index 518c30665e4..0b658ff2626 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java @@ -69,6 +69,8 @@ public void clearColumnOrder(final @Nullable Identifier identifier) { * @param columnListing if null, clears the map entry */ public void putColumnOrder(final @Nullable Identifier identifier, @Nullable final Iterable columnListing) { + if(identifier==null) + return; if(columnListing==null) { clearColumnOrder(identifier); return; diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java index e518fb2508b..e45c3391e9a 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAssociationContainer.java @@ -137,8 +137,12 @@ default Stream streamCollections(final MixedIn mixedIn){ /** - * Properties and Collections visible as columns, honoring order and visibility. + * Bundles parameters to query for Properties and Collections visible as columns. + * + * @param memberIdentifier not used for standalone tables * @param parentObject not used for standalone tables and allowed to be empty for parented ones + * + * @since 4.0 */ record ColumnQuery( @Nullable Identifier memberIdentifier, diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java index 5547157e68c..916a6f1ee8d 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MembersAsColumns.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -169,7 +170,7 @@ private Optional> propertyIdComparator( */ private boolean sortColumnsUsingPatch( final ColumnQuery columnQuery, - final List assocIdsInOrder, + final List assocIdsInOrder, //mutable final ObjectSpecification elementType) { if(!isColumnOrderPatchingEnabled) @@ -187,12 +188,19 @@ private boolean sortColumnsUsingPatch( if(patchedColumnOrder==null) return false; + // intersect 'assocIdsInOrder' with 'patchedColumnOrder' while preserving order as given by the latter + var available = new HashSet<>(assocIdsInOrder); + assocIdsInOrder.clear(); + patchedColumnOrder.stream() + .filter(available::contains) + .forEach(assocIdsInOrder::add); + return true; } private void sortColumnsUsingSpi( final ColumnQuery columnQuery, - final List assocIdsInOrder, + final List assocIdsInOrder, //mutable final ObjectSpecification elementType) { if(tableColumnOrderServices.isEmpty()) From 760f9f811a7d66f13681a05b9a089f8d864b9f4f Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 16 May 2026 11:40:59 +0200 Subject: [PATCH 17/18] CAUSEWAY-3997: wire up Listing; flesh out patching --- .../columnorder/Object_patchColumnOrder.java | 66 +++++++++++-------- .../services/metamodel/MetaModelService.java | 8 +++ .../columnorder/ColumnOrderPatchingFacet.java | 4 +- .../metamodel/MetaModelServiceDefault.java | 26 +++++++- 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java index 52acfdd243c..118dcdb5949 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/columnorder/Object_patchColumnOrder.java @@ -19,7 +19,7 @@ package org.apache.causeway.applib.services.columnorder; import java.util.List; -import java.util.stream.Collectors; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import jakarta.inject.Inject; @@ -44,6 +44,10 @@ import org.apache.causeway.applib.services.appfeat.ApplicationFeatureRepository; import org.apache.causeway.applib.services.metamodel.MetaModelService; import org.apache.causeway.applib.services.metamodel.MetaModelService.AssociationsLookup; +import org.apache.causeway.applib.util.Listing; +import org.apache.causeway.applib.util.Listing.ListingHandler; +import org.apache.causeway.applib.util.Listing.MergePolicy; +import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.internal.exceptions._Exceptions; import lombok.RequiredArgsConstructor; @@ -96,11 +100,15 @@ public static class ActionDomainEvent @Parameter(precedingParamsPolicy = PrecedingParamsPolicy.RESET) @ParameterLayout(multiLine = 20) - final String columnDefinition) { - // TODO flesh out: we need some holder of column order overrides (patches) - // make sure, - // org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.streamAssociationsForColumnRendering(Identifier, ManagedObject) - // honors any patches. Also patching must override any SPI, because that also is a use-case for patching. + final String columnListing) { + + var identifier = applicationFeatureRepository.asIdentifier(featureId) + .orElseThrow(); // not found -> unexpected + + var listing = listingHandler().parseListing(columnListing); + var columns = Can.ofStream(listing.streamEnabled()); + + metaModelService.patchColumnOrder(identifier, columns); return mixee; } @@ -113,44 +121,44 @@ public static class ActionDomainEvent .toList(); } - @MemberSupport public String defaultColumnDefinition(final @Nullable ApplicationFeatureId appFeatureId) { - if(appFeatureId==null) + @MemberSupport public String defaultColumnListing(final @Nullable ApplicationFeatureId featureId) { + if(featureId==null) return "# no feature selected"; - var featureId = applicationFeatureRepository.asIdentifier(appFeatureId) + var identifier = applicationFeatureRepository.asIdentifier(featureId) .orElseThrow(); // not found -> unexpected - if(featureId.type().isCollection()) + if(identifier.type().isCollection()) return listing( - metaModelService.parentedAssociationsForColumnRendering(mixee, featureId, AssociationsLookup.AVAILABLE), - metaModelService.parentedAssociationsForColumnRendering(mixee, featureId, AssociationsLookup.ENABLED)); + metaModelService.parentedAssociationsForColumnRendering(mixee, identifier, AssociationsLookup.AVAILABLE), + metaModelService.parentedAssociationsForColumnRendering(mixee, identifier, AssociationsLookup.ENABLED)) + .toString(); - if(featureId.type().isClass()) + if(identifier.type().isClass()) return listing( - metaModelService.standaloneAssociationsForColumnRendering(featureId.logicalType(), AssociationsLookup.AVAILABLE), - metaModelService.standaloneAssociationsForColumnRendering(featureId.logicalType(), AssociationsLookup.ENABLED)); + metaModelService.standaloneAssociationsForColumnRendering(identifier.logicalType(), AssociationsLookup.AVAILABLE), + metaModelService.standaloneAssociationsForColumnRendering(identifier.logicalType(), AssociationsLookup.ENABLED)) + .toString(); - throw _Exceptions.illegalArgument("unsupported feature type %s", featureId.type()); + throw _Exceptions.illegalArgument("unsupported feature type %s", identifier.type()); } // -- HELPER - private String listing(final Stream availableIds, final Stream enabledIds) { + private Listing listing(final Stream availableIds, final Stream enabledIds) { // all column candidates - var available = availableIds - .map(Identifier::memberLogicalName) - .collect(Collectors.joining("\n")); + var available = listingHandler() + .createListing(availableIds.map(Identifier::memberLogicalName)); // all columns currently rendered - var enabled = enabledIds - .map(Identifier::memberLogicalName) - .collect(Collectors.joining("\n")); - - // TODO flesh out using Listing? - return "# available\n" - + available - + "\n\n# enabled\n" - + enabled; + var enabled = listingHandler() + .createListing(enabledIds.map(Identifier::memberLogicalName)); + + return enabled.merge(MergePolicy.ADD_NEW_AS_DISABLED, available); + } + + private final static ListingHandler listingHandler() { + return new ListingHandler<>(String.class, UnaryOperator.identity(), UnaryOperator.identity(), UnaryOperator.identity()); } } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java index ff96d891951..0b7fa221059 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/MetaModelService.java @@ -254,4 +254,12 @@ public enum AssociationsLookup { */ Stream standaloneAssociationsForColumnRendering(LogicalType logicalType, AssociationsLookup lookup); + /** + * @param identifier either for a TYPE or a COLLECTION + * @param columnsInOrder to be used instead of the default order + * + * @since 4.0 + */ + void patchColumnOrder(Identifier identifier, Can columnsInOrder); + } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java index 0b658ff2626..f300ba9ee80 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/layout/columnorder/ColumnOrderPatchingFacet.java @@ -68,14 +68,14 @@ public void clearColumnOrder(final @Nullable Identifier identifier) { * @param identifier if null, acts as a no-op * @param columnListing if null, clears the map entry */ - public void putColumnOrder(final @Nullable Identifier identifier, @Nullable final Iterable columnListing) { + public void putColumnOrder(final @Nullable Identifier identifier, @Nullable final Can columnListing) { if(identifier==null) return; if(columnListing==null) { clearColumnOrder(identifier); return; } - columnOrder.put(identifier, Can.ofIterable(columnListing)); + columnOrder.put(identifier, columnListing); } public Optional> lookupColumnOrder(final @Nullable Identifier identifier) { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java index 35e23603f51..bc8e8284742 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java @@ -27,7 +27,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import jakarta.annotation.Priority; import jakarta.inject.Named; import jakarta.inject.Provider; @@ -38,7 +37,6 @@ import org.springframework.stereotype.Service; import org.apache.causeway.applib.Identifier; -import org.apache.causeway.applib.annotation.PriorityPrecedence; import org.apache.causeway.applib.id.LogicalType; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureSort; @@ -56,6 +54,8 @@ import org.apache.causeway.commons.internal.collections._Lists; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; +import org.apache.causeway.core.metamodel.facetapi.FacetUtil; +import org.apache.causeway.core.metamodel.facets.collections.layout.columnorder.ColumnOrderPatchingFacet; import org.apache.causeway.core.metamodel.facets.members.publish.command.CommandPublishingFacet; import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.object.ManagedObjects; @@ -79,7 +79,6 @@ */ @Service @Named(CausewayModuleCoreMetamodel.NAMESPACE + ".MetaModelServiceDefault") -@Priority(PriorityPrecedence.MIDPOINT) @Qualifier("Default") public record MetaModelServiceDefault( Provider specificationLoaderProvider, @@ -349,4 +348,25 @@ public Stream standaloneAssociationsForColumnRendering( .map(ObjectAssociation::getFeatureIdentifier); } + @Override + public void patchColumnOrder(final Identifier identifier, final Can columnsInOrder) { + final ObjectSpecification elementType = switch (identifier.type()) { + case CLASS -> specificationLoader() + .specForLogicalType(identifier.logicalType()) + .orElseThrow(); + case COLLECTION -> specificationLoader() + .specForLogicalType(identifier.logicalType()) + .map(parentType->parentType.getCollectionElseFail(identifier.memberLogicalName())) + .map(OneToManyAssociation::getElementType) + .orElseThrow(); + case ACTION, ACTION_PARAMETER, PROPERTY -> + throw new UnsupportedOperationException("Unimplemented case: " + identifier.type()); + }; + + var columnOrderPatchingFacet = elementType.lookupFacet(ColumnOrderPatchingFacet.class) + .orElseGet(()->FacetUtil.addFacet(new ColumnOrderPatchingFacet(elementType))); + + columnOrderPatchingFacet.putColumnOrder(identifier, columnsInOrder); + } + } From 19fb3e72e26b13e2d7ab624afa0e8f6529b09187 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 16 May 2026 11:45:26 +0200 Subject: [PATCH 18/18] CAUSEWAY-3997: test approvals --- ...sViewer_IntegTest.dump_facets.approved.xml | 10 +- ...nDomain_IntegTest.dump_facets.approved.xml | 10 +- ...sViewer_IntegTest.dump_facets.approved.xml | 10 +- ...pDomain_IntegTest.dump_facets.approved.xml | 10 +- ...etaModelRegressionTest.verify.approved.xml | 300 +++++++++--------- 5 files changed, 170 insertions(+), 170 deletions(-) diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml index 60bd015b252..84cb83bd6b1 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml @@ -1189,14 +1189,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -1222,8 +1222,8 @@ - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml index cfa8406b634..4e9f79c403c 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml @@ -1182,14 +1182,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -1215,8 +1215,8 @@ - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml index bee5da58f66..6516805d8bf 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomainWithPdfjsViewer_IntegTest.dump_facets.approved.xml @@ -1178,14 +1178,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -1211,8 +1211,8 @@ - - + + diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml index b52df45b557..1b6236accf0 100644 --- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml +++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_PropDomain_IntegTest.dump_facets.approved.xml @@ -1171,14 +1171,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -1204,8 +1204,8 @@ - - + + diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index 2b67dedfcbb..25814a79ff7 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -1024,14 +1024,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -1045,8 +1045,8 @@ - - + + @@ -2240,14 +2240,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -2261,8 +2261,8 @@ - - + + @@ -3701,14 +3701,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -3722,8 +3722,8 @@ - - + + @@ -4989,14 +4989,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -5010,8 +5010,8 @@ - - + + @@ -6265,14 +6265,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -6286,8 +6286,8 @@ - - + + @@ -7854,14 +7854,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -7875,8 +7875,8 @@ - - + + @@ -9581,14 +9581,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -9602,8 +9602,8 @@ - - + + @@ -11500,14 +11500,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -11521,8 +11521,8 @@ - - + + @@ -12902,14 +12902,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -12923,8 +12923,8 @@ - - + + @@ -14288,14 +14288,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -14309,8 +14309,8 @@ - - + + @@ -15696,14 +15696,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -15717,8 +15717,8 @@ - - + + @@ -17129,14 +17129,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -17150,8 +17150,8 @@ - - + + @@ -18625,14 +18625,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -18646,8 +18646,8 @@ - - + + @@ -20281,14 +20281,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -20302,8 +20302,8 @@ - - + + @@ -21866,14 +21866,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -21887,8 +21887,8 @@ - - + + @@ -23162,14 +23162,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -23183,8 +23183,8 @@ - - + + @@ -24458,14 +24458,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -24479,8 +24479,8 @@ - - + + @@ -28570,14 +28570,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -28591,8 +28591,8 @@ - - + + @@ -29997,14 +29997,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -30018,8 +30018,8 @@ - - + + @@ -31656,14 +31656,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -31677,8 +31677,8 @@ - - + + @@ -33319,14 +33319,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -33340,8 +33340,8 @@ - - + + @@ -38704,14 +38704,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -38725,8 +38725,8 @@ - - + + @@ -41621,14 +41621,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -41642,8 +41642,8 @@ - - + + @@ -42912,14 +42912,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -42933,8 +42933,8 @@ - - + + @@ -44292,14 +44292,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -44313,8 +44313,8 @@ - - + + @@ -46010,14 +46010,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -46031,8 +46031,8 @@ - - + + @@ -47650,14 +47650,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -47671,8 +47671,8 @@ - - + + @@ -49290,14 +49290,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -49311,8 +49311,8 @@ - - + + @@ -50976,14 +50976,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -50997,8 +50997,8 @@ - - + + @@ -52428,14 +52428,14 @@ org.apache.causeway.applib.services.appfeat.ApplicationFeatureId - + - + - + @@ -52449,8 +52449,8 @@ - - + +