This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch spring6
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/spring6 by this push:
new 4589789949 CAUSEWAY-3682: [Commons] YamlUtils to support Java Records
4589789949 is described below
commit 4589789949266b730970781173e967d8d5850659
Author: Andi Huber <[email protected]>
AuthorDate: Thu Jan 25 16:24:13 2024 +0100
CAUSEWAY-3682: [Commons] YamlUtils to support Java Records
---
commons/pom.xml | 11 ++-
commons/src/main/java/module-info.java | 1 +
.../org/apache/causeway/commons/io/JsonUtils.java | 18 +++--
.../org/apache/causeway/commons/io/YamlUtils.java | 43 +++++++----
.../apache/causeway/commons/io/JaxbUtilsTest.java | 84 +++++++++-------------
.../apache/causeway/commons/io/JsonUtilsTest.java | 62 ++++++++++++++++
...sTest.toStringUtf8_with_no_options.approved.txt | 1 +
.../apache/causeway/commons/io/YamlUtilsTest.java | 57 +++++++++++++++
...sTest.toStringUtf8_with_no_options.approved.txt | 2 +
.../apache/causeway/commons/io/_TestDomain.java | 39 ++++++++++
10 files changed, 242 insertions(+), 76 deletions(-)
diff --git a/commons/pom.xml b/commons/pom.xml
index 979eec0111..fccea5e91f 100644
--- a/commons/pom.xml
+++ b/commons/pom.xml
@@ -118,12 +118,14 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
-
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ </dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>
</dependency>
-
<dependency>
<groupId>com.fasterxml.jackson.jakarta.rs</groupId>
<artifactId>jackson-jakarta-rs-json-provider</artifactId>
@@ -215,6 +217,11 @@
<artifactId>hamcrest-library</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.approvaltests</groupId>
+ <artifactId>approvaltests</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
diff --git a/commons/src/main/java/module-info.java
b/commons/src/main/java/module-info.java
index 2438711d90..9dea6a4db5 100644
--- a/commons/src/main/java/module-info.java
+++ b/commons/src/main/java/module-info.java
@@ -76,6 +76,7 @@ module org.apache.causeway.commons {
requires transitive jakarta.inject;
requires jakarta.annotation;
requires com.sun.xml.bind;
+ requires com.fasterxml.jackson.dataformat.yaml;
// JAXB JUnit test
opens org.apache.causeway.commons.internal.resources to jakarta.xml.bind;
diff --git
a/commons/src/main/java/org/apache/causeway/commons/io/JsonUtils.java
b/commons/src/main/java/org/apache/causeway/commons/io/JsonUtils.java
index 63046b5132..b6b7997e4e 100644
--- a/commons/src/main/java/org/apache/causeway/commons/io/JsonUtils.java
+++ b/commons/src/main/java/org/apache/causeway/commons/io/JsonUtils.java
@@ -63,7 +63,7 @@ public class JsonUtils {
}
@FunctionalInterface
- public interface JsonCustomizer extends UnaryOperator<ObjectMapper> {}
+ public interface JacksonCustomizer extends UnaryOperator<ObjectMapper> {}
// -- READING
@@ -74,7 +74,7 @@ public class JsonUtils {
public <T> Try<T> tryRead(
final @NonNull Class<T> mappedType,
final @Nullable String stringUtf8,
- final JsonUtils.JsonCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
return tryRead(mappedType, DataSource.ofStringUtf8(stringUtf8),
customizers);
}
@@ -85,7 +85,7 @@ public class JsonUtils {
public <T> Try<T> tryRead(
final @NonNull Class<T> mappedType,
final @NonNull DataSource source,
- final JsonUtils.JsonCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
return source.tryReadAll((final InputStream is)->{
return Try.call(()->createMapper(customizers).readValue(is,
mappedType));
});
@@ -98,7 +98,7 @@ public class JsonUtils {
public <T> Try<List<T>> tryReadAsList(
final @NonNull Class<T> elementType,
final @NonNull DataSource source,
- final JsonUtils.JsonCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
return source.tryReadAll((final InputStream is)->{
return Try.call(()->{
val mapper = createMapper(customizers);
@@ -116,7 +116,7 @@ public class JsonUtils {
public void write(
final @Nullable Object pojo,
final @NonNull DataSink sink,
- final JsonUtils.JsonCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
if(pojo==null) return;
sink.writeAll(os->
Try.run(()->createMapper(customizers).writeValue(os, pojo)));
@@ -130,7 +130,7 @@ public class JsonUtils {
@Nullable
public static String toStringUtf8(
final @Nullable Object pojo,
- final JsonUtils.JsonCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
return pojo!=null
? createMapper(customizers).writeValueAsString(pojo)
: null;
@@ -156,15 +156,13 @@ public class JsonUtils {
// -- MAPPER FACTORY
private ObjectMapper createMapper(
- final JsonUtils.JsonCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
var mapper = new ObjectMapper();
- for(JsonUtils.JsonCustomizer customizer : customizers) {
+ for(JsonUtils.JacksonCustomizer customizer : customizers) {
mapper = Optional.ofNullable(customizer.apply(mapper))
.orElse(mapper);
}
return mapper;
}
-
-
}
diff --git
a/commons/src/main/java/org/apache/causeway/commons/io/YamlUtils.java
b/commons/src/main/java/org/apache/causeway/commons/io/YamlUtils.java
index 5463fc2ef4..d650e12526 100644
--- a/commons/src/main/java/org/apache/causeway/commons/io/YamlUtils.java
+++ b/commons/src/main/java/org/apache/causeway/commons/io/YamlUtils.java
@@ -30,6 +30,9 @@ import java.util.Map;
import java.util.Optional;
import java.util.function.UnaryOperator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
import org.springframework.lang.Nullable;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.LineBreak;
@@ -62,12 +65,13 @@ import lombok.experimental.UtilityClass;
@UtilityClass
public class YamlUtils {
+ /**
+ * @deprecated We rely on Jackson to parse YAML. Might also replace
SnakeYaml with Jackson to write YAML.
+ */
+ @Deprecated
@FunctionalInterface
public interface YamlDumpCustomizer extends UnaryOperator<DumperOptions> {}
- @FunctionalInterface
- public interface YamlLoadCustomizer extends UnaryOperator<LoaderOptions> {}
-
// -- READING
/**
@@ -77,7 +81,7 @@ public class YamlUtils {
public <T> Try<T> tryRead(
final @NonNull Class<T> mappedType,
final @Nullable String stringUtf8,
- final YamlUtils.YamlLoadCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
return tryRead(mappedType, DataSource.ofStringUtf8(stringUtf8),
customizers);
}
@@ -88,10 +92,10 @@ public class YamlUtils {
public <T> Try<T> tryRead(
final @NonNull Class<T> mappedType,
final @NonNull DataSource source,
- final YamlUtils.YamlLoadCustomizer ... customizers) {
+ final JsonUtils.JacksonCustomizer ... customizers) {
return source.tryReadAll((final InputStream is)->{
- return Try.call(()->createMapper(mappedType,
Can.ofArray(customizers), Can.empty())
- .load(is));
+ return Try.call(()->createJacksonMapperForYaml(customizers)
+ .readValue(is, mappedType));
});
}
@@ -106,7 +110,7 @@ public class YamlUtils {
final YamlUtils.YamlDumpCustomizer ... customizers) {
if(pojo==null) return;
sink.writeAll(os->
- createMapper(pojo.getClass(), Can.empty(),
Can.ofArray(customizers)).dump(pojo, new OutputStreamWriter(os)));
+ createMapper(pojo.getClass(), Can.ofArray(customizers)).dump(pojo,
new OutputStreamWriter(os)));
}
/**
@@ -119,7 +123,7 @@ public class YamlUtils {
final @Nullable Object pojo,
final YamlUtils.YamlDumpCustomizer ... customizers) {
return pojo!=null
- ? createMapper(pojo.getClass(), Can.empty(),
Can.ofArray(customizers)).dump(pojo)
+ ? createMapper(pojo.getClass(),
Can.ofArray(customizers)).dump(pojo)
: null;
}
@@ -134,11 +138,24 @@ public class YamlUtils {
return opts;
}
- // -- MAPPER FACTORY
+ // -- MAPPER FACTORIES
+
+ /**
+ * SnakeYaml as of 2.2 does not support Java records. So we use Jackson
instead.
+ */
+ private ObjectMapper createJacksonMapperForYaml(
+ final JsonUtils.JacksonCustomizer ... customizers) {
+ var mapper = new ObjectMapper(new YAMLFactory());
+ for(JsonUtils.JacksonCustomizer customizer : customizers) {
+ mapper = Optional.ofNullable(customizer.apply(mapper))
+ .orElse(mapper);
+ }
+ return mapper;
+ }
+
private Yaml createMapper(
final Class<?> mappedType,
- final Can<YamlUtils.YamlLoadCustomizer> loadCustomizers,
final Can<YamlUtils.YamlDumpCustomizer> dumpCustomizers) {
var dumperOptions = new DumperOptions();
dumperOptions.setIndent(2);
@@ -154,10 +171,6 @@ public class YamlUtils {
presenter.addClassTag(mappedType, Tag.MAP);
var loaderOptions = new LoaderOptions();
- for(YamlUtils.YamlLoadCustomizer customizer : loadCustomizers) {
- loaderOptions =
Optional.ofNullable(customizer.apply(loaderOptions))
- .orElse(loaderOptions);
- }
var mapper = new Yaml(new Constructor(mappedType, loaderOptions),
presenter, dumperOptions, loaderOptions);
return mapper;
}
diff --git
a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
b/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
index 69238a9eff..6fb8a33a00 100644
--- a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
@@ -18,6 +18,8 @@
*/
package org.apache.causeway.commons.io;
+import javax.xml.transform.TransformerFactory;
+
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
@@ -25,10 +27,7 @@ import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
-//import javax.xml.transform.TransformerFactory;
-//
-//import
org.apache.causeway.commons.io.JaxbUtils.JaxbOptions.JaxbOptionsBuilder;
-//import org.approvaltests.Approvals;
+import org.approvaltests.Approvals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -37,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.commons.io.JaxbUtils.JaxbOptions.JaxbOptionsBuilder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -109,50 +109,36 @@ class JaxbUtilsTest {
}
}
- // commenting this out only because javac v11 fails to compile. However,
javac v20 handles it, so we can uncomment in the future.
- // https://the-asf.slack.com/archives/CFC42LWBV/p1694771499860429
-
-// @Test
-// void toStringUtf8_with_no_options() {
-//
-// val aXml = JaxbUtils.toStringUtf8(a);
-//
-// System.out.println(aXml);
-//
-// Approvals.verify(aXml);
-// }
-//
-// @Test
-// void toStringUtf8_with_no_formatted_output() {
-//
-// val aXml = JaxbUtils.toStringUtf8(a, opt -> {
-// opt.formattedOutput(false);
-// return opt;
-// });
-//
-// System.out.println(aXml);
-//
-// Approvals.verify(aXml);
-// }
-//
-// @Test
-// void toStringUtf8_with_indent_number_overridden() {
-//
-// val aXml = JaxbUtils.toStringUtf8(a, new
JaxbUtils.TransformerFactoryCustomizer() {
-// @Override
-// public void apply(TransformerFactory transformerFactory) {
-// transformerFactory.setAttribute("indent-number", 2);
-// }
-//
-// @Override
-// public JaxbOptionsBuilder apply(JaxbOptionsBuilder
jaxbOptionsBuilder) {
-// return jaxbOptionsBuilder;
-// }
-// });
-//
-// System.out.println(aXml);
-//
-// Approvals.verify(aXml);
-// }
+ @Test
+ void toStringUtf8_with_no_options() {
+ val aXml = JaxbUtils.toStringUtf8(a);
+ Approvals.verify(aXml);
+ }
+
+ @Test
+ void toStringUtf8_with_no_formatted_output() {
+ val aXml = JaxbUtils.toStringUtf8(a, opt -> {
+ opt.formattedOutput(false);
+ return opt;
+ });
+ Approvals.verify(aXml);
+ }
+
+ @Test
+ void toStringUtf8_with_indent_number_overridden() {
+ val aXml = JaxbUtils.toStringUtf8(a, new
JaxbUtils.TransformerFactoryCustomizer() {
+ @Override
+ public void apply(final TransformerFactory transformerFactory) {
+ transformerFactory.setAttribute("indent-number", 2);
+ }
+
+ @Override
+ public JaxbOptionsBuilder apply(final JaxbOptionsBuilder
jaxbOptionsBuilder) {
+ return jaxbOptionsBuilder;
+ }
+
+ });
+ Approvals.verify(aXml);
+ }
}
diff --git
a/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.java
b/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.java
new file mode 100644
index 0000000000..fa2239b4aa
--- /dev/null
+++ b/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.commons.io;
+
+import org.approvaltests.Approvals;
+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 lombok.val;
+
+class JsonUtilsTest {
+
+ private Person person;
+
+ @BeforeEach
+ void setup() {
+ this.person = _TestDomain.samplePerson();
+ }
+
+ @Test
+ void toStringUtf8_with_no_options() {
+ val json = JsonUtils.toStringUtf8(person);
+ Approvals.verify(json);
+ }
+
+ @Test
+ void parseRecord() {
+ var json = """
+ {
+ "name":"sven",
+ "address": {
+ "zip":1234,
+ "street":"backerstreet"
+ }
+ }
+ """;
+ var person = JsonUtils.tryRead(Person.class, json)
+ .valueAsNonNullElseFail();
+ assertEquals(this.person, person);
+ }
+
+}
diff --git
a/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.toStringUtf8_with_no_options.approved.txt
b/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.toStringUtf8_with_no_options.approved.txt
new file mode 100644
index 0000000000..120f413192
--- /dev/null
+++
b/commons/src/test/java/org/apache/causeway/commons/io/JsonUtilsTest.toStringUtf8_with_no_options.approved.txt
@@ -0,0 +1 @@
+{"name":"sven","address":{"zip":1234,"street":"backerstreet"}}
\ No newline at end of file
diff --git
a/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
b/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
new file mode 100644
index 0000000000..daa17948ed
--- /dev/null
+++ b/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.commons.io;
+
+import org.approvaltests.Approvals;
+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 lombok.val;
+
+class YamlUtilsTest {
+
+ private Person person;
+
+ @BeforeEach
+ void setup() {
+ this.person = _TestDomain.samplePerson();
+ }
+
+ @Test
+ void toStringUtf8_with_no_options() {
+ val yaml = YamlUtils.toStringUtf8(person);
+ Approvals.verify(yaml);
+ }
+
+ @Test
+ void parseRecord() {
+ var yaml = """
+ name: sven
+ address: {street: backerstreet, zip: 1234}
+ """;
+ var person = YamlUtils.tryRead(Person.class, yaml)
+ .valueAsNonNullElseFail();
+ assertEquals(this.person, person);
+ }
+
+}
diff --git
a/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8_with_no_options.approved.txt
b/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8_with_no_options.approved.txt
new file mode 100644
index 0000000000..bbfcdd376f
--- /dev/null
+++
b/commons/src/test/java/org/apache/causeway/commons/io/YamlUtilsTest.toStringUtf8_with_no_options.approved.txt
@@ -0,0 +1,2 @@
+address: {street: backerstreet, zip: 1234}
+name: sven
diff --git
a/commons/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
b/commons/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
new file mode 100644
index 0000000000..2e4bb7523c
--- /dev/null
+++ b/commons/src/test/java/org/apache/causeway/commons/io/_TestDomain.java
@@ -0,0 +1,39 @@
+/*
+ * 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.commons.io;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+class _TestDomain {
+
+ public static record Person(
+ String name,
+ Address address) {
+ }
+
+ public static record Address(
+ int zip,
+ String street) {
+ }
+
+ Person samplePerson() {
+ return new Person("sven", new Address(1234, "backerstreet"));
+ }
+}