tryRead(
/**
* Tries to deserialize YAML content from given {@link DataSource} into a {@link List}
* with given {@code elementType}.
+ *
+ * Either parses (regular) YAML-list format or multi-doc YAML format.
*/
public Try> tryReadAsList(
final @NonNull Class elementType,
final @NonNull DataSource source,
final JsonUtils.JacksonCustomizer ... customizers) {
- return source.tryReadAll((final InputStream is) -> Try.call(()->{
- var mapper = createJacksonReader(Optional.empty(), customizers);
- var collectionType = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
- return mapper.readValue(is, collectionType);
- }));
+ return tryReadAsListCustomized(elementType, source, null, customizers);
}
/**
@@ -121,20 +127,58 @@ public Try tryReadCustomized(
/**
* Tries to deserialize YAML content from given {@link DataSource} into a {@link List}
- * with given {@code elementType}.
+ * with given {@code elementType}.
+ *
+ * Either parses (regular) YAML-list format or multi-doc YAML format.
*/
public Try> tryReadAsListCustomized(
final @NonNull Class elementType,
final @NonNull DataSource source,
- final @NonNull YamlLoadCustomizer loadCustomizer,
+ final @Nullable YamlLoadCustomizer loadCustomizer,
final JsonUtils.JacksonCustomizer ... customizers) {
return source.tryReadAll((final InputStream is) -> Try.call(()->{
- var mapper = createJacksonReader(Optional.of(loadCustomizer), customizers);
- var collectionType = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
- return mapper.readValue(is, collectionType);
+ var mapper = createJacksonReader(Optional.ofNullable(loadCustomizer), customizers);
+ final MappingIterator documentReader = mapper.readerFor(elementType).readValues(is);
+ final List elements = new ArrayList<>();
+ while (documentReader.hasNextValue()) {
+ final T next = documentReader.nextValue();
+ if (next != null) {
+ elements.add(next);
+ }
+ }
+ return elements; // no need wrap unmodifiable, as callers can do what ever they want with the list
+//former code without support for multi-doc format...
+// var collectionType = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
+// return mapper.readValue(is, collectionType);
}));
}
-
+
+ /**
+ * Returns a {@link Stream} of (arbitrary) YAML Document junks from provided {@link DataSource},
+ * where each junk typically needs further parsing individually.
+ */
+ public Try> tryReadMultiDoc(final @Nullable DataSource source) {
+ return source == null
+ ? Try.success(Stream.empty())
+ : source.tryReadAsStringUtf8()
+ .mapSuccessAsNullable(TextUtils::readLines)
+ .mapSuccessAsNullable(YamlUtils::linesToDocs);
+ }
+ private static Stream linesToDocs(final Can lineStream) {
+ var buffer = new ArrayList();
+ buffer.add(new StringBuilder());
+ for(var line : lineStream) {
+ if(line.equals(MULTI_DOC_DELIMITER)) {
+ buffer.add(new StringBuilder());
+ continue;
+ }
+ buffer.get(buffer.size()-1).append(line).append("\n");
+ }
+ return buffer.stream()
+ .map(StringBuilder::toString)
+ .filter(str->!str.isEmpty());
+ }
+
// -- WRITING
/**
@@ -144,7 +188,9 @@ public void write(
final @Nullable Object pojo,
final @NonNull DataSink sink,
final JsonUtils.JacksonCustomizer ... customizers) {
- if(pojo==null) return;
+ if(pojo==null) {
+ return;
+ }
sink.writeAll(os->
Try.run(()->createJacksonWriter(Optional.empty(), customizers).writeValue(os, pojo)));
}
@@ -171,7 +217,9 @@ public void writeCustomized(
final @NonNull DataSink sink,
final @NonNull YamlDumpCustomizer dumpCustomizer,
final JsonUtils.JacksonCustomizer ... customizers) {
- if(pojo==null) return;
+ if(pojo==null) {
+ return;
+ }
sink.writeAll(os->
Try.run(()->createJacksonWriter(Optional.of(dumpCustomizer), customizers).writeValue(os, pojo)));
}
@@ -182,7 +230,7 @@ public void writeCustomized(
*/
@SneakyThrows
@Nullable
- public static String toStringUtf8Customized(
+ public String toStringUtf8Customized(
final @Nullable Object pojo,
final @NonNull YamlDumpCustomizer dumpCustomizer,
final JsonUtils.JacksonCustomizer ... customizers) {
@@ -191,6 +239,21 @@ public static String toStringUtf8Customized(
: null;
}
+ /**
+ * Concatenates given {@link Iterable} into a multi-doc YAML.
+ */
+ public String writeMultiDoc(@Nullable Iterable yamlDocuments) {
+ return _NullSafe.stream(yamlDocuments)
+ .collect(Collectors.joining(MULTI_DOC_DELIMITER + "\n"));
+ }
+ /**
+ * Concatenates given {@link Stream} into a multi-doc YAML.
+ */
+ public String writeMultiDoc(@Nullable Stream yamlDocumentStream) {
+ return _NullSafe.stream(yamlDocumentStream)
+ .collect(Collectors.joining(MULTI_DOC_DELIMITER + "\n"));
+ }
+
// -- CUSTOMIZERS
/**
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/.gitignore b/commons/src/test/java/org/apache/causeway/commons/io/.gitignore
deleted file mode 100644
index e8bd71526ca..00000000000
--- a/commons/src/test/java/org/apache/causeway/commons/io/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/*.received.txt
diff --git a/core/internaltestsupport/pom.xml b/core/internaltestsupport/pom.xml
index a7425ef1e55..a752f1140a1 100644
--- a/core/internaltestsupport/pom.xml
+++ b/core/internaltestsupport/pom.xml
@@ -112,6 +112,18 @@ additional
org.slf4j
slf4j-api
+
+
+
+
+ com.approvaltests
+ approvaltests
+
+
+
+ com.google.code.gson
+ gson
diff --git a/core/mmtest/.gitignore b/core/mmtest/.gitignore
new file mode 100644
index 00000000000..e66bc565654
--- /dev/null
+++ b/core/mmtest/.gitignore
@@ -0,0 +1 @@
+*.received.txt
\ No newline at end of file
diff --git a/core/mmtest/pom.xml b/core/mmtest/pom.xml
index 1f9627c8c6c..9c0ed7b6869 100644
--- a/core/mmtest/pom.xml
+++ b/core/mmtest/pom.xml
@@ -12,41 +12,61 @@ additional
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License. -->
- 4.0.0
-
-
- org.apache.causeway.core
- causeway-core
- 4.0.0-SNAPSHOT
-
-
- causeway-core-mmtest
- Apache Causeway Core - Metamodel Test
-
-
- org.apache.causeway.core.mmtest
- org/apache/causeway/core/mmtest
- true
-
+
-
- Tests the causeway-core-metamodel artifact (using a custom
- MetaModelContext for testing, as provided by
- causeway-core-mmtestsupport).
- Introduced to avoid a circular artifact dependency.
-
+
+ Tests the causeway-core-metamodel artifact (using a custom
+ MetaModelContext for testing, as provided by
+ causeway-core-mmtestsupport).
+ Introduced to avoid a circular artifact dependency.
+
-
+
+
+ org.apache.causeway.core
+ causeway-core-mmtestsupport
+ test
+
+
+ org.apache.causeway.testing
+ causeway-testing-unittestsupport-applib
+ test
+
+
+ org.apache.causeway.core
+ causeway-core-internaltestsupport
+ test
+
+
+ org.apache.causeway.testing
+ causeway-testing-integtestsupport-applib
+
- org.apache.causeway.core
- causeway-core-mmtestsupport
+
+ com.google.code.gson
+ gson
test
-
+
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Approval_Test.java b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Approval_Test.java
new file mode 100644
index 00000000000..26ae6cb2493
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Approval_Test.java
@@ -0,0 +1,103 @@
+/*
+ * 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.util.schema;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.util.StreamUtils;
+
+import org.apache.causeway.commons.io.DataSource;
+import org.apache.causeway.schema.cmd.v2.ActionDto;
+import org.apache.causeway.schema.cmd.v2.CommandDto;
+import org.apache.causeway.schema.cmd.v2.ParamDto;
+import org.apache.causeway.schema.common.v2.ValueType;
+
+class CommandDtoUtils_fromYaml_Approval_Test {
+
+ @Test
+ void unmarshals_all_date_time_datatypes_from_approved_toYaml_snapshot() throws IOException {
+ String yaml = readApprovalSnapshot();
+
+ List commands = CommandDtoUtils.fromYaml(DataSource.ofStringUtf8(yaml));
+
+ Assertions.assertThat(commands).singleElement().satisfies(command -> {
+ Assertions.assertThat(command.getInteractionId()).isEqualTo("approval-datetime-marshalling");
+
+ ActionDto action = (ActionDto) command.getMember();
+ Assertions.assertThat(action.getLogicalMemberIdentifier())
+ .isEqualTo("demo.Customer#allDateTimeTypes");
+
+ List params = action.getParameters().getParameter();
+ Assertions.assertThat(params).hasSize(6);
+
+ ParamDto localDate = params.get(0);
+ Assertions.assertThat(localDate.getType()).isEqualTo(ValueType.LOCAL_DATE);
+ Assertions.assertThat(localDate.getLocalDate().toXMLFormat()).isEqualTo("2026-07-01");
+
+ ParamDto localDateTime = params.get(1);
+ Assertions.assertThat(localDateTime.getType()).isEqualTo(ValueType.LOCAL_DATE_TIME);
+ Assertions.assertThat(localDateTime.getLocalDateTime().toXMLFormat()).isEqualTo("2026-07-01T10:15:30");
+
+ ParamDto localTime = params.get(2);
+ Assertions.assertThat(localTime.getType()).isEqualTo(ValueType.LOCAL_TIME);
+ Assertions.assertThat(localTime.getLocalTime().toXMLFormat()).isEqualTo("10:15:30");
+
+ ParamDto offsetDateTime = params.get(3);
+ Assertions.assertThat(offsetDateTime.getType()).isEqualTo(ValueType.OFFSET_DATE_TIME);
+ Assertions.assertThat(offsetDateTime.getOffsetDateTime().getYear()).isEqualTo(2026);
+ Assertions.assertThat(offsetDateTime.getOffsetDateTime().getMonth()).isEqualTo(7);
+ Assertions.assertThat(offsetDateTime.getOffsetDateTime().getDay()).isEqualTo(1);
+ Assertions.assertThat(offsetDateTime.getOffsetDateTime().getHour()).isEqualTo(8);
+ Assertions.assertThat(offsetDateTime.getOffsetDateTime().getMinute()).isEqualTo(15);
+ Assertions.assertThat(offsetDateTime.getOffsetDateTime().getSecond()).isEqualTo(30);
+ Assertions.assertThat(offsetDateTime.getOffsetDateTime().getTimezone()).isEqualTo(0);
+
+ ParamDto offsetTime = params.get(4);
+ Assertions.assertThat(offsetTime.getType()).isEqualTo(ValueType.OFFSET_TIME);
+ Assertions.assertThat(offsetTime.getOffsetTime()).isNotNull();
+ Assertions.assertThat(offsetTime.getOffsetTime().getHour()).isEqualTo(8);
+ Assertions.assertThat(offsetTime.getOffsetTime().getMinute()).isEqualTo(15);
+ Assertions.assertThat(offsetTime.getOffsetTime().getSecond()).isEqualTo(30);
+ Assertions.assertThat(offsetTime.getOffsetTime().getTimezone()).isEqualTo(0);
+
+ ParamDto zonedDateTime = params.get(5);
+ Assertions.assertThat(zonedDateTime.getType()).isEqualTo(ValueType.ZONED_DATE_TIME);
+ Assertions.assertThat(zonedDateTime.getZonedDateTime().getYear()).isEqualTo(2026);
+ Assertions.assertThat(zonedDateTime.getZonedDateTime().getMonth()).isEqualTo(7);
+ Assertions.assertThat(zonedDateTime.getZonedDateTime().getDay()).isEqualTo(1);
+ Assertions.assertThat(zonedDateTime.getZonedDateTime().getHour()).isEqualTo(8);
+ Assertions.assertThat(zonedDateTime.getZonedDateTime().getMinute()).isEqualTo(15);
+ Assertions.assertThat(zonedDateTime.getZonedDateTime().getSecond()).isEqualTo(30);
+ Assertions.assertThat(zonedDateTime.getZonedDateTime().getTimezone())
+ .isEqualTo(0);
+ });
+ }
+
+ private String readApprovalSnapshot() throws IOException {
+ String path = CommandDtoUtils_toYaml_Approval_Test.class.getSimpleName() + ".marshals_all_date_time_datatypes.approved.txt";
+ InputStream stream = CommandDtoUtils_toYaml_Approval_Test.class.getResourceAsStream(path);
+ return StreamUtils.copyToString(stream, java.nio.charset.StandardCharsets.UTF_8);
+ }
+
+}
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-collection-param.yaml b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-collection-param.yaml
new file mode 100644
index 00000000000..4175c718be9
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-collection-param.yaml
@@ -0,0 +1,92 @@
+- majorVersion: "2"
+ minorVersion: "0"
+ interactionId: "87ded048-530d-41b4-a431-59df0a77899d"
+ timestamp: "2026-04-21T09:09:06.882+00:00"
+ username: "estatio-admin"
+ targets:
+ oid:
+ - type: "outgoing.lease.Lease"
+ id: "32846"
+ member: !
+ parameters:
+ parameter:
+ - enum:
+ enumType: "org.estatio.module.invoice.dom.InvoiceRunType"
+ enumName: "NORMAL_RUN"
+ type: "enum"
+ name: "Run Type"
+ - collection:
+ value:
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "RENT"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "RENT_FIXED"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "RENT_DISCOUNT"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "RENT_DISCOUNT_FIXED"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "SERVICE_CHARGE"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "SERVICE_CHARGE_INDEXABLE"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "SERVICE_CHARGE_DISCOUNT_FIXED"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "DEPOSIT"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "TAX"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "MARKETING"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "PROPERTY_TAX"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "OFFICE_TAX"
+ type: "enum"
+ - enum:
+ enumType: "org.estatio.module.lease.dom.LeaseItemType"
+ enumName: "RETAIL_TAX"
+ type: "enum"
+ type: "enum"
+ type: "collection"
+ "null": false
+ name: "Lease Item Types"
+ - localDate: "2026-06-30"
+ type: "localDate"
+ name: "Invoice Due Date"
+ - localDate: "2026-06-30"
+ type: "localDate"
+ name: "Start Due Date"
+ - localDate: "2026-07-01"
+ type: "localDate"
+ name: "Next Due Date"
+ - type: "string"
+ "null": true
+ name: "Tag Name"
+ - string: "JDOJPA-T1"
+ type: "string"
+ name: "New Tag Name"
+ logicalMemberIdentifier: "outgoing.lease.Lease#calculate"
+ interactionType: "action_invocation"
\ No newline at end of file
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-scalar-params-multi-document.yaml b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-scalar-params-multi-document.yaml
new file mode 100644
index 00000000000..8ad1657ae59
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-scalar-params-multi-document.yaml
@@ -0,0 +1,34 @@
+majorVersion: "2"
+minorVersion: "0"
+interactionId: "08c210e1-55c0-4c45-83db-491f6581e621"
+timestamp: "2026-04-21T09:43:58.169+00:00"
+username: "estatio-admin"
+targets:
+ oid:
+ - type: "outgoing.invoiceforlease.InvoiceForLease"
+ id: "419264"
+member: !
+ logicalMemberIdentifier: "outgoing.invoiceforlease.InvoiceForLease#approve"
+ interactionType: "action_invocation"
+---
+majorVersion: "2"
+minorVersion: "0"
+interactionId: "0e2ad15e-c1d8-48c5-8b63-b8cdde673715"
+timestamp: "2026-04-21T09:45:07.409+00:00"
+username: "estatio-admin"
+targets:
+ oid:
+ - type: "outgoing.invoiceforlease.InvoiceForLease"
+ id: "419264"
+member: !
+ parameters:
+ parameter:
+ - localDate: "2026-04-20"
+ type: "localDate"
+ name: "Invoice Date"
+ - boolean: false
+ type: "boolean"
+ name: "Allow Invoice Date In Future"
+ logicalMemberIdentifier: "outgoing.invoiceforlease.InvoiceForLease#invoice"
+ interactionType: "action_invocation"
+
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-scalar-params.yaml b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-scalar-params.yaml
new file mode 100644
index 00000000000..81222d72ad7
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.commands-with-scalar-params.yaml
@@ -0,0 +1,32 @@
+- majorVersion: "2"
+ minorVersion: "0"
+ interactionId: "08c210e1-55c0-4c45-83db-491f6581e621"
+ timestamp: "2026-04-21T09:43:58.169+00:00"
+ username: "estatio-admin"
+ targets:
+ oid:
+ - type: "outgoing.invoiceforlease.InvoiceForLease"
+ id: "419264"
+ member: !
+ logicalMemberIdentifier: "outgoing.invoiceforlease.InvoiceForLease#approve"
+ interactionType: "action_invocation"
+- majorVersion: "2"
+ minorVersion: "0"
+ interactionId: "0e2ad15e-c1d8-48c5-8b63-b8cdde673715"
+ timestamp: "2026-04-21T09:45:07.409+00:00"
+ username: "estatio-admin"
+ targets:
+ oid:
+ - type: "outgoing.invoiceforlease.InvoiceForLease"
+ id: "419264"
+ member: !
+ parameters:
+ parameter:
+ - localDate: "2026-04-20"
+ type: "localDate"
+ name: "Invoice Date"
+ - boolean: false
+ type: "boolean"
+ name: "Allow Invoice Date In Future"
+ logicalMemberIdentifier: "outgoing.invoiceforlease.InvoiceForLease#invoice"
+ interactionType: "action_invocation"
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.java b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.java
new file mode 100644
index 00000000000..9faa5a3c43b
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_fromYaml_Test.java
@@ -0,0 +1,162 @@
+/*
+ * 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.util.schema;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.util.StreamUtils;
+
+import org.apache.causeway.applib.value.Blob;
+import org.apache.causeway.applib.value.NamedWithMimeType;
+import org.apache.causeway.schema.cmd.v2.ActionDto;
+import org.apache.causeway.schema.cmd.v2.CommandDto;
+import org.apache.causeway.schema.cmd.v2.ParamDto;
+import org.apache.causeway.schema.common.v2.InteractionType;
+import org.apache.causeway.schema.common.v2.ValueType;
+
+class CommandDtoUtils_fromYaml_Test {
+
+ @Test
+ void scalarValues() throws IOException {
+ var commandDtos = loadCommands("commands-with-scalar-params.yaml");
+
+ assertScalarCommands(commandDtos);
+ }
+
+ @Test
+ void scalarValuesAsMultiDocument() throws IOException {
+ var commandDtos = loadCommands("commands-with-scalar-params-multi-document.yaml");
+
+ assertScalarCommands(commandDtos);
+ }
+
+ private static void assertScalarCommands(final List commandDtos) {
+
+ Assertions.assertThat(commandDtos).hasSize(2);
+
+ ActionDto firstAction = (ActionDto) commandDtos.get(0).getMember();
+ Assertions.assertThat(firstAction.getLogicalMemberIdentifier())
+ .isEqualTo("outgoing.invoiceforlease.InvoiceForLease#approve");
+ Assertions.assertThat(firstAction.getInteractionType())
+ .isEqualTo(InteractionType.ACTION_INVOCATION);
+ Assertions.assertThat(commandDtos.get(0).getTargets().getOid())
+ .singleElement()
+ .satisfies(oid -> {
+ Assertions.assertThat(oid.getType()).isEqualTo("outgoing.invoiceforlease.InvoiceForLease");
+ Assertions.assertThat(oid.getId()).isEqualTo("419264");
+ });
+
+ ActionDto secondAction = (ActionDto) commandDtos.get(1).getMember();
+ Assertions.assertThat(secondAction.getLogicalMemberIdentifier())
+ .isEqualTo("outgoing.invoiceforlease.InvoiceForLease#invoice");
+ Assertions.assertThat(secondAction.getInteractionType())
+ .isEqualTo(InteractionType.ACTION_INVOCATION);
+
+ List scalarParams = secondAction.getParameters().getParameter();
+ Assertions.assertThat(scalarParams).hasSize(2);
+
+ ParamDto invoiceDate = scalarParams.get(0);
+ Assertions.assertThat(invoiceDate.getName()).isEqualTo("Invoice Date");
+ Assertions.assertThat(invoiceDate.getType()).isEqualTo(ValueType.LOCAL_DATE);
+ Assertions.assertThat(invoiceDate.getLocalDate()).isNotNull();
+ Assertions.assertThat(invoiceDate.getLocalDate().toXMLFormat()).isEqualTo("2026-04-20");
+
+ ParamDto allowFuture = scalarParams.get(1);
+ Assertions.assertThat(allowFuture.getName()).isEqualTo("Allow Invoice Date In Future");
+ Assertions.assertThat(allowFuture.getType()).isEqualTo(ValueType.BOOLEAN);
+ Assertions.assertThat(allowFuture.isBoolean()).isFalse();
+ }
+
+ @Test
+ void collectionValues() throws IOException {
+ var commandDtos = loadCommands("commands-with-collection-param.yaml");
+
+ Assertions.assertThat(commandDtos).hasSize(1);
+
+ ActionDto action = (ActionDto) commandDtos.get(0).getMember();
+ Assertions.assertThat(action.getLogicalMemberIdentifier())
+ .isEqualTo("outgoing.lease.Lease#calculate");
+ Assertions.assertThat(action.getInteractionType())
+ .isEqualTo(InteractionType.ACTION_INVOCATION);
+
+ List params = action.getParameters().getParameter();
+ Assertions.assertThat(params).hasSize(7);
+
+ ParamDto leaseItemTypes = params.get(1);
+ Assertions.assertThat(leaseItemTypes.getName()).isEqualTo("Lease Item Types");
+ Assertions.assertThat(leaseItemTypes.getType()).isEqualTo(ValueType.COLLECTION);
+ Assertions.assertThat(leaseItemTypes.isNull()).isFalse();
+ Assertions.assertThat(leaseItemTypes.getCollection()).isNotNull();
+ Assertions.assertThat(leaseItemTypes.getCollection().getType()).isEqualTo(ValueType.ENUM);
+
+ var items = leaseItemTypes.getCollection().getValue();
+ Assertions.assertThat(items).hasSize(13);
+ Assertions.assertThat(items).allSatisfy(item -> Assertions.assertThat(item.getEnum()).isNotNull());
+
+ Assertions.assertThat(items.get(0).getEnum().getEnumType())
+ .isEqualTo("org.estatio.module.lease.dom.LeaseItemType");
+ Assertions.assertThat(items.get(0).getEnum().getEnumName()).isEqualTo("RENT");
+
+ Assertions.assertThat(items.get(6).getEnum().getEnumName()).isEqualTo("SERVICE_CHARGE_DISCOUNT_FIXED");
+ Assertions.assertThat(items.get(12).getEnum().getEnumName()).isEqualTo("RETAIL_TAX");
+
+ ParamDto nullableTagName = params.get(5);
+ Assertions.assertThat(nullableTagName.getName()).isEqualTo("Tag Name");
+ Assertions.assertThat(nullableTagName.getType()).isEqualTo(ValueType.STRING);
+ Assertions.assertThat(nullableTagName.isNull()).isTrue();
+
+ ParamDto invoiceDueDate = params.get(2);
+ Assertions.assertThat(invoiceDueDate.getName()).isEqualTo("Invoice Due Date");
+ Assertions.assertThat(invoiceDueDate.getType()).isEqualTo(ValueType.LOCAL_DATE);
+ Assertions.assertThat(invoiceDueDate.getLocalDate().toXMLFormat()).isEqualTo("2026-06-30");
+
+ ParamDto startDueDate = params.get(3);
+ Assertions.assertThat(startDueDate.getName()).isEqualTo("Start Due Date");
+ Assertions.assertThat(startDueDate.getType()).isEqualTo(ValueType.LOCAL_DATE);
+ Assertions.assertThat(startDueDate.getLocalDate().toXMLFormat()).isEqualTo("2026-06-30");
+
+ ParamDto nextDueDate = params.get(4);
+ Assertions.assertThat(nextDueDate.getName()).isEqualTo("Next Due Date");
+ Assertions.assertThat(nextDueDate.getType()).isEqualTo(ValueType.LOCAL_DATE);
+ Assertions.assertThat(nextDueDate.getLocalDate().toXMLFormat()).isEqualTo("2026-07-01");
+
+ ParamDto newTagName = params.get(6);
+ Assertions.assertThat(newTagName.getName()).isEqualTo("New Tag Name");
+ Assertions.assertThat(newTagName.getType()).isEqualTo(ValueType.STRING);
+ Assertions.assertThat(newTagName.getString()).isEqualTo("JDOJPA-T1");
+ }
+
+ private List loadCommands(final String yamlFileName) throws IOException {
+ InputStream resourceAsStream = getClass().getResourceAsStream(getClass().getSimpleName() + "." + yamlFileName);
+ byte[] bytes = StreamUtils.copyToByteArray(resourceAsStream);
+
+ Blob commandsYaml = new Blob(
+ yamlFileName,
+ NamedWithMimeType.CommonMimeType.YAML.mimeType(),
+ bytes);
+
+ return CommandDtoUtils.fromYaml(commandsYaml.asDataSource());
+ }
+
+}
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_Approval_Test.java b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_Approval_Test.java
new file mode 100644
index 00000000000..e77583704d5
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_Approval_Test.java
@@ -0,0 +1,172 @@
+/*
+ * 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.util.schema;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+import org.approvaltests.Approvals;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.util.StreamUtils;
+
+import org.apache.causeway.schema.cmd.v2.ActionDto;
+import org.apache.causeway.schema.cmd.v2.CommandDto;
+import org.apache.causeway.schema.cmd.v2.ParamDto;
+import org.apache.causeway.schema.cmd.v2.ParamsDto;
+import org.apache.causeway.schema.common.v2.InteractionType;
+import org.apache.causeway.schema.common.v2.OidDto;
+import org.apache.causeway.schema.common.v2.OidsDto;
+import org.apache.causeway.schema.common.v2.ValueType;
+import org.apache.causeway.testing.integtestsupport.applib.ApprovalsOptions;
+
+/**
+ * @implNote potential issues with ISO-8601 format equivalence of
+ * {@code 2026-07-01T08:15:30.000+00:00 == 2026-07-01T08:15:30.000Z},
+ * tests might fail, because tests expect one or the other in a hardcoded way
+ */
+class CommandDtoUtils_toYaml_Approval_Test {
+
+ private static final DatatypeFactory DATATYPE_FACTORY = datatypeFactory();
+
+ @Test
+ void marshals_all_date_time_datatypes() {
+ withDefaultTimeZone("UTC", () -> {
+ String yaml = CommandDtoUtils.toMultiDocYaml(List.of(commandWithAllDateTimeParams()));
+ Approvals.verify(yaml, ApprovalsOptions.defaultOptions());
+ });
+ }
+
+ @Test
+ void marshals_all_date_time_datatypes_when_default_timezone_is_cest() {
+ withDefaultTimeZone("Europe/Paris", () -> {
+ String yaml = CommandDtoUtils.toMultiDocYaml(List.of(commandWithAllDateTimeParams()));
+ try {
+ Assertions.assertThat(yaml).isEqualTo(readApprovalSnapshot());
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ });
+ }
+
+ private static CommandDto commandWithAllDateTimeParams() {
+ CommandDto command = new CommandDto();
+ command.setMajorVersion("2");
+ command.setMinorVersion("0");
+ command.setInteractionId("approval-datetime-marshalling");
+ command.setUsername("approval-user");
+ command.setTargets(targets("demo.Customer", "123"));
+ command.setMember(actionWithAllDateTimeParams());
+ return command;
+ }
+
+ private static void withDefaultTimeZone(final String zoneId, final _Runnable runnable) {
+ TimeZone originalDefault = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
+ try {
+ runnable.run();
+ } finally {
+ TimeZone.setDefault(originalDefault);
+ }
+ }
+
+ @FunctionalInterface
+ private interface _Runnable {
+ void run();
+ }
+
+ private String readApprovalSnapshot() throws IOException {
+ String path = getClass().getSimpleName() + ".marshals_all_date_time_datatypes.approved.txt";
+ InputStream stream = getClass().getResourceAsStream(path);
+ return StreamUtils.copyToString(stream, StandardCharsets.UTF_8);
+ }
+
+ private static ActionDto actionWithAllDateTimeParams() {
+ ActionDto action = new ActionDto();
+ action.setLogicalMemberIdentifier("demo.Customer#allDateTimeTypes");
+ action.setInteractionType(InteractionType.ACTION_INVOCATION);
+
+ ParamsDto params = new ParamsDto();
+ params.getParameter().add(param("Local Date", ValueType.LOCAL_DATE, "2026-07-01"));
+ params.getParameter().add(param("Local Date Time", ValueType.LOCAL_DATE_TIME, "2026-07-01T10:15:30"));
+ params.getParameter().add(param("Local Time", ValueType.LOCAL_TIME, "10:15:30"));
+ params.getParameter().add(param("Offset Date Time", ValueType.OFFSET_DATE_TIME, "2026-07-01T10:15:30+02:00"));
+ params.getParameter().add(param("Offset Time", ValueType.OFFSET_TIME, "10:15:30+02:00"));
+ params.getParameter().add(param("Zoned Date Time", ValueType.ZONED_DATE_TIME, "2026-07-01T10:15:30+02:00"));
+ action.setParameters(params);
+ return action;
+ }
+
+ private static ParamDto param(final String name, final ValueType type, final String lexicalValue) {
+ ParamDto param = new ParamDto();
+ param.setName(name);
+ param.setType(type);
+
+ XMLGregorianCalendar value = DATATYPE_FACTORY.newXMLGregorianCalendar(lexicalValue);
+ switch (type) {
+ case LOCAL_DATE:
+ param.setLocalDate(value);
+ break;
+ case LOCAL_DATE_TIME:
+ param.setLocalDateTime(value);
+ break;
+ case LOCAL_TIME:
+ param.setLocalTime(value);
+ break;
+ case OFFSET_DATE_TIME:
+ param.setOffsetDateTime(value);
+ break;
+ case OFFSET_TIME:
+ param.setOffsetTime(value);
+ break;
+ case ZONED_DATE_TIME:
+ param.setZonedDateTime(value);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported type: " + type);
+ }
+ return param;
+ }
+
+ private static OidsDto targets(final String type, final String id) {
+ OidDto oid = new OidDto();
+ oid.setType(type);
+ oid.setId(id);
+
+ OidsDto targets = new OidsDto();
+ targets.getOid().add(oid);
+ return targets;
+ }
+
+ private static DatatypeFactory datatypeFactory() {
+ try {
+ return DatatypeFactory.newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException("Failed to initialize DatatypeFactory", ex);
+ }
+ }
+
+}
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_Approval_Test.marshals_all_date_time_datatypes.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_Approval_Test.marshals_all_date_time_datatypes.approved.txt
new file mode 100644
index 00000000000..4b844c701fb
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_Approval_Test.marshals_all_date_time_datatypes.approved.txt
@@ -0,0 +1,31 @@
+majorVersion: "2"
+minorVersion: "0"
+interactionId: "approval-datetime-marshalling"
+username: "approval-user"
+targets:
+ oid:
+ - id: "123"
+ type: "demo.Customer"
+member: !
+ parameters:
+ parameter:
+ - localDate: "2026-07-01"
+ name: "Local Date"
+ type: "localDate"
+ - localDateTime: "2026-07-01T10:15:30"
+ name: "Local Date Time"
+ type: "localDateTime"
+ - localTime: "10:15:30"
+ name: "Local Time"
+ type: "localTime"
+ - name: "Offset Date Time"
+ offsetDateTime: "2026-07-01T08:15:30.000Z"
+ type: "offsetDateTime"
+ - name: "Offset Time"
+ offsetTime: "1970-01-01T08:15:30.000Z"
+ type: "offsetTime"
+ - name: "Zoned Date Time"
+ type: "zonedDateTime"
+ zonedDateTime: "2026-07-01T08:15:30.000Z"
+ interactionType: "action_invocation"
+ logicalMemberIdentifier: "demo.Customer#allDateTimeTypes"
diff --git a/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_fromYaml_Test.java b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_fromYaml_Test.java
new file mode 100644
index 00000000000..f4b68ec1197
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_fromYaml_Test.java
@@ -0,0 +1,133 @@
+/*
+ * 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.util.schema;
+
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.xml.datatype.DatatypeConstants;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import org.apache.causeway.commons.io.DataSource;
+import org.apache.causeway.schema.cmd.v2.ActionDto;
+import org.apache.causeway.schema.cmd.v2.CommandDto;
+import org.apache.causeway.schema.cmd.v2.ParamDto;
+import org.apache.causeway.schema.cmd.v2.ParamsDto;
+import org.apache.causeway.schema.common.v2.InteractionType;
+import org.apache.causeway.schema.common.v2.OidDto;
+import org.apache.causeway.schema.common.v2.OidsDto;
+import org.apache.causeway.schema.common.v2.ValueType;
+
+class CommandDtoUtils_toYaml_fromYaml_Test {
+
+ @Test
+ void localDate_roundtrips_as_date_only_without_timezone() throws Exception {
+ TimeZone originalDefault = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("Europe/Amsterdam"));
+ try {
+ DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
+ XMLGregorianCalendar originalDate = datatypeFactory
+ .newXMLGregorianCalendarDate(2026, 7, 1, DatatypeConstants.FIELD_UNDEFINED);
+ XMLGregorianCalendar originalDateTime = datatypeFactory
+ .newXMLGregorianCalendar("2026-07-01T10:15:30");
+ XMLGregorianCalendar originalTime = datatypeFactory
+ .newXMLGregorianCalendar("10:15:30");
+
+ CommandDto command = new CommandDto();
+ command.setMajorVersion("2");
+ command.setMinorVersion("0");
+ command.setInteractionId("localdate-roundtrip-bug");
+ command.setUsername("sven");
+
+ OidDto oid = new OidDto();
+ oid.setType("demo.Customer");
+ oid.setId("123");
+ OidsDto targets = new OidsDto();
+ targets.getOid().add(oid);
+ command.setTargets(targets);
+
+ ParamDto localDateParam = new ParamDto();
+ localDateParam.setName("Invoice Date");
+ localDateParam.setType(ValueType.LOCAL_DATE);
+ localDateParam.setLocalDate(originalDate);
+
+ ParamDto localDateTimeParam = new ParamDto();
+ localDateTimeParam.setName("Invoice Date Time");
+ localDateTimeParam.setType(ValueType.LOCAL_DATE_TIME);
+ localDateTimeParam.setLocalDateTime(originalDateTime);
+
+ ParamDto localTimeParam = new ParamDto();
+ localTimeParam.setName("Invoice Time");
+ localTimeParam.setType(ValueType.LOCAL_TIME);
+ localTimeParam.setLocalTime(originalTime);
+
+ ParamsDto params = new ParamsDto();
+ params.getParameter().add(localDateParam);
+ params.getParameter().add(localDateTimeParam);
+ params.getParameter().add(localTimeParam);
+
+ ActionDto action = new ActionDto();
+ action.setLogicalMemberIdentifier("demo.Customer#invoice");
+ action.setInteractionType(InteractionType.ACTION_INVOCATION);
+ action.setParameters(params);
+ command.setMember(action);
+
+ String yaml = CommandDtoUtils.toMultiDocYaml(List.of(command));
+ List roundtripped = CommandDtoUtils.fromYaml(DataSource.ofStringUtf8(yaml));
+
+ XMLGregorianCalendar roundtrippedDate = ((ActionDto) roundtripped.get(0).getMember())
+ .getParameters()
+ .getParameter()
+ .get(0)
+ .getLocalDate();
+ XMLGregorianCalendar roundtrippedDateTime = ((ActionDto) roundtripped.get(0).getMember())
+ .getParameters()
+ .getParameter()
+ .get(1)
+ .getLocalDateTime();
+ XMLGregorianCalendar roundtrippedTime = ((ActionDto) roundtripped.get(0).getMember())
+ .getParameters()
+ .getParameter()
+ .get(2)
+ .getLocalTime();
+
+ // Verify fixed behavior: local date/time values are emitted and roundtripped without timezone.
+ Assertions.assertThat(yaml)
+ .contains("localDate: \"2026-07-01\"")
+ .contains("localDateTime: \"2026-07-01T10:15:30\"")
+ .contains("localTime: \"10:15:30\"")
+ .doesNotContain("localDate: \"2026-06-30T22:00:00.000+00:00\"")
+ .doesNotContain("localDateTime: \"2026-07-01T10:15:30.000+00:00\"")
+ .doesNotContain("localTime: \"1970-01-01T10:15:30.000+00:00\"");
+ Assertions.assertThat(roundtrippedDate.toXMLFormat())
+ .isEqualTo(originalDate.toXMLFormat());
+ Assertions.assertThat(roundtrippedDateTime.toXMLFormat())
+ .isEqualTo(originalDateTime.toXMLFormat());
+ Assertions.assertThat(roundtrippedTime.toXMLFormat())
+ .isEqualTo(originalTime.toXMLFormat());
+ } finally {
+ TimeZone.setDefault(originalDefault);
+ }
+ }
+
+}
diff --git a/core/mmtest/src/test/java/org/apache/causeway/mmtest/schema/CommandDtoYamlRoundtripTest.java b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoYamlRoundtripTest.java
similarity index 91%
rename from core/mmtest/src/test/java/org/apache/causeway/mmtest/schema/CommandDtoYamlRoundtripTest.java
rename to core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoYamlRoundtripTest.java
index 382bff2f850..f29fa262396 100644
--- a/core/mmtest/src/test/java/org/apache/causeway/mmtest/schema/CommandDtoYamlRoundtripTest.java
+++ b/core/mmtest/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoYamlRoundtripTest.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.causeway.mmtest.schema;
+package org.apache.causeway.applib.util.schema;
import java.sql.Timestamp;
import java.time.Instant;
@@ -42,11 +42,18 @@
class CommandDtoYamlRoundtripTest {
@Test
- void test() {
+ void list() {
var yaml = CommandDtoUtils.toYaml(List.of(commandDtoSample(), commandDtoSample()));
var afterRoundtrip = CommandDtoUtils.fromYaml(DataSource.ofStringUtf8(yaml));
assertEquals(yaml, CommandDtoUtils.toYaml(afterRoundtrip));
}
+
+ @Test
+ void multiDoc() {
+ var yaml = CommandDtoUtils.toMultiDocYaml(List.of(commandDtoSample(), commandDtoSample()));
+ var afterRoundtrip = CommandDtoUtils.fromYaml(DataSource.ofStringUtf8(yaml));
+ assertEquals(yaml, CommandDtoUtils.toMultiDocYaml(afterRoundtrip));
+ }
private CommandDto commandDtoSample() {
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/DataSourceTest.java b/core/mmtest/src/test/java/org/apache/causeway/commons/io/DataSourceTest.java
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/DataSourceTest.java
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/DataSourceTest.java
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java b/core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_indent_number_overridden.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_indent_number_overridden.approved.txt
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_indent_number_overridden.approved.txt
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_indent_number_overridden.approved.txt
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_formatted_output.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_formatted_output.approved.txt
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_formatted_output.approved.txt
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_formatted_output.approved.txt
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_options.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_options.approved.txt
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_options.approved.txt
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.toStringUtf8_with_no_options.approved.txt
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.java b/core/mmtest/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.java
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.java
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.java
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.toStringUtf8_indentedOutput.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.toStringUtf8_indentedOutput.approved.txt
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.toStringUtf8_indentedOutput.approved.txt
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.toStringUtf8_indentedOutput.approved.txt
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java b/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
similarity index 64%
rename from commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
index f15406b08bc..8596d72fb58 100644
--- a/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
+++ b/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
@@ -18,21 +18,30 @@
*/
package org.apache.causeway.commons.io;
+import java.util.List;
+
import org.approvaltests.Approvals;
+import org.approvaltests.reporters.DiffReporter;
+import org.approvaltests.reporters.UseReporter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.apache.causeway.commons.io._TestDomain.Person;
+import org.apache.causeway.testing.integtestsupport.applib.ApprovalsOptions;
class YamlUtilsTest {
private Person person;
+ private List persons;
@BeforeEach
void setup() {
this.person = _TestDomain.samplePerson();
+ this.persons = List.of(
+ person,
+ _TestDomain.samplePerson("fred"));
}
@Test
@@ -72,5 +81,35 @@ void parseRecord() {
.valueAsNonNullElseFail();
assertEquals(this.person, person);
}
+
+ @Test
+ @UseReporter(DiffReporter.class)
+ void toStringUtf8ForList_yamlList() {
+ var yaml = YamlUtils.toStringUtf8(persons);
+ Approvals.verify(yaml, ApprovalsOptions.defaultOptions());
+ }
+ @Test
+ @UseReporter(DiffReporter.class)
+ void toStringUtf8ForList_multiDoc() {
+ var yaml = YamlUtils.writeMultiDoc(
+ persons.stream()
+ .map(YamlUtils::toStringUtf8));
+ Approvals.verify(yaml, ApprovalsOptions.defaultOptions());
+ }
+
+ @Test
+ void multiDocRoundtrip() {
+ var multiDocYaml = YamlUtils.writeMultiDoc(
+ persons.stream()
+ .map(YamlUtils::toStringUtf8));
+ var personsAfterRoundtrip = YamlUtils.tryReadMultiDoc(DataSource.ofStringUtf8(multiDocYaml))
+ .valueAsNonNullElseFail()
+ .map(yaml->YamlUtils.tryRead(Person.class, yaml)
+ .valueAsNonNullElseFail())
+ .toList();
+
+ assertEquals(this.persons, personsAfterRoundtrip);
+ }
+
}
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8.approved.txt
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8.approved.txt
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8.approved.txt
diff --git a/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8ForList_multiDoc.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8ForList_multiDoc.approved.txt
new file mode 100644
index 00000000000..3cac21f215c
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8ForList_multiDoc.approved.txt
@@ -0,0 +1,39 @@
+name: "sven"
+address:
+ zip: 1234
+ street: "backerstreet"
+additionalAddresses:
+- zip: 23
+ street: "brownstreet"
+- zip: 34
+ street: "bluestreet"
+java8Time:
+ localTime: "17:33:45"
+ localDate: "2007-11-21"
+ localDateTime: "2007-11-21T17:33:45"
+ offsetTime: "17:33:45-02:00"
+ offsetDateTime: "2007-11-21T17:33:45-02:00"
+ zonedDateTime: "2007-11-21T17:33:45+01:00"
+phone:
+ home: "+99 1234"
+ work: null
+---
+name: "fred"
+address:
+ zip: 1234
+ street: "backerstreet"
+additionalAddresses:
+- zip: 23
+ street: "brownstreet"
+- zip: 34
+ street: "bluestreet"
+java8Time:
+ localTime: "17:33:45"
+ localDate: "2007-11-21"
+ localDateTime: "2007-11-21T17:33:45"
+ offsetTime: "17:33:45-02:00"
+ offsetDateTime: "2007-11-21T17:33:45-02:00"
+ zonedDateTime: "2007-11-21T17:33:45+01:00"
+phone:
+ home: "+99 1234"
+ work: null
diff --git a/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8ForList_yamlList.approved.txt b/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8ForList_yamlList.approved.txt
new file mode 100644
index 00000000000..935533db17e
--- /dev/null
+++ b/core/mmtest/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8ForList_yamlList.approved.txt
@@ -0,0 +1,38 @@
+- name: "sven"
+ address:
+ zip: 1234
+ street: "backerstreet"
+ additionalAddresses:
+ - zip: 23
+ street: "brownstreet"
+ - zip: 34
+ street: "bluestreet"
+ java8Time:
+ localTime: "17:33:45"
+ localDate: "2007-11-21"
+ localDateTime: "2007-11-21T17:33:45"
+ offsetTime: "17:33:45-02:00"
+ offsetDateTime: "2007-11-21T17:33:45-02:00"
+ zonedDateTime: "2007-11-21T17:33:45+01:00"
+ phone:
+ home: "+99 1234"
+ work: null
+- name: "fred"
+ address:
+ zip: 1234
+ street: "backerstreet"
+ additionalAddresses:
+ - zip: 23
+ street: "brownstreet"
+ - zip: 34
+ street: "bluestreet"
+ java8Time:
+ localTime: "17:33:45"
+ localDate: "2007-11-21"
+ localDateTime: "2007-11-21T17:33:45"
+ offsetTime: "17:33:45-02:00"
+ offsetDateTime: "2007-11-21T17:33:45-02:00"
+ zonedDateTime: "2007-11-21T17:33:45+01:00"
+ phone:
+ home: "+99 1234"
+ work: null
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/ZipUtilsTest.java b/core/mmtest/src/test/java/org/apache/causeway/commons/io/ZipUtilsTest.java
similarity index 100%
rename from commons/src/test/java/org/apache/causeway/commons/io/ZipUtilsTest.java
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/ZipUtilsTest.java
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/_TestDomain.java b/core/mmtest/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
similarity index 96%
rename from commons/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
rename to core/mmtest/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
index 1181e75ce8f..084396ef686 100644
--- a/commons/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
+++ b/core/mmtest/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
@@ -96,7 +96,11 @@ public static record Java8TimeStringified(
}
Person samplePerson() {
- return new Person("sven", new Address(1234, "backerstreet"),
+ return samplePerson("sven");
+ }
+
+ Person samplePerson(String name) {
+ return new Person(name, new Address(1234, "backerstreet"),
Can.of(new Address(23, "brownstreet"),
new Address(34, "bluestreet")),
new Java8Time(
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
index df351356b5e..878f545f81d 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
@@ -102,7 +102,7 @@ public Blob exportSelected(
.sorted()
.toList();
- var yaml = CommandDtoUtils.toYaml(
+ var yaml = CommandDtoUtils.toMultiDocYaml(
selectedCommandLogEntries.stream()
.filter(entry->!ReplayState.isExported(entry.getReplayState()))
.map(CommandLogEntry::getCommandDto)
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
index 0868182867c..f734e487f96 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
@@ -114,8 +114,9 @@ public CommandReplayManager replayOrRetrySelected(final List
.toList();
for(var replayableCommand : replayables) {
var tryReplayOrRetry = replayableCommand.tryReplayOrRetry(); // filtered on its own responsibility
- if(tryReplayOrRetry.isFailure())
- return this; // stop further execution
+ if(tryReplayOrRetry.isFailure()) {
+ return this; // stop further execution
+ }
}
return this;
}