From 0be54674618a86cead6806e7a25f8a53abf9436a Mon Sep 17 00:00:00 2001 From: Florian Krupicka Date: Mon, 17 Feb 2020 01:42:00 +0100 Subject: [PATCH] Add abstraction for a CoffeeNet view model Multiple parts of CoffeeNet contribute UI/UX components to the final application. This abstraction allows to write these in a technology-agnostic way. --- coffeenet-platform-domain/pom.xml | 6 + .../platform/domain/web/CoffeeNetModel.java | 109 ++++++++++++++++++ .../domain/web/CoffeeNetModelContributor.java | 17 +++ .../web/CoffeeNetModelContributorTests.java | 86 ++++++++++++++ 4 files changed, 218 insertions(+) create mode 100644 coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModel.java create mode 100644 coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributor.java create mode 100644 coffeenet-platform-domain/src/test/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributorTests.java diff --git a/coffeenet-platform-domain/pom.xml b/coffeenet-platform-domain/pom.xml index 0abacf7c..23c1cb8c 100644 --- a/coffeenet-platform-domain/pom.xml +++ b/coffeenet-platform-domain/pom.xml @@ -22,6 +22,12 @@ + + + com.fasterxml.jackson.core + jackson-annotations + + org.springframework.boot diff --git a/coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModel.java b/coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModel.java new file mode 100644 index 00000000..292a4990 --- /dev/null +++ b/coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModel.java @@ -0,0 +1,109 @@ +package rocks.coffeenet.platform.domain.web; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; + +import java.util.AbstractMap; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + + +/** + * This view model makes common attributes available to the view layer in web applications. It is made available under + * the name {@link CoffeeNetModel#MODEL_NAME}. + * + *

Each attribute element can be singular or a hierarchical object like a POJO or nested Map.

+ * + * @author Florian 'punycode' Krupicka - zh@punyco.de + */ +public class CoffeeNetModel extends AbstractMap { + + public static final String MODEL_NAME = "coffeenet"; + + private final Map attributes; + + private CoffeeNetModel(Builder builder) { + + this.attributes = Collections.unmodifiableMap(builder.content); + } + + @Override + public Set> entrySet() { + + return attributes.entrySet(); + } + + + @JsonAnyGetter + Map getAttributes() { + + return attributes; + } + + + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass() || !super.equals(o)) { + return false; + } + + return Objects.equals(attributes, ((CoffeeNetModel) o).attributes); + } + + + @Override + public int hashCode() { + + return Objects.hash(super.hashCode(), attributes); + } + + /** + * Builder for creating immutable {@link CoffeeNetModel} instances. + */ + public static class Builder { + + private final Map content; + + public Builder() { + + this.content = new LinkedHashMap<>(); + } + + /** + * Contribute a view model property using given {@code key} and {@code value}. + */ + public Builder withDetail(String key, Object value) { + + this.content.put(key, value); + + return this; + } + + + /** + * Contribute multiple view model properties given the contents of {@code details}. + */ + public Builder withDetails(Map details) { + + this.content.putAll(details); + + return this; + } + + + /** + * Create a new {@link CoffeeNetModel} instance based on the state of this builder. + */ + public CoffeeNetModel build() { + + return new CoffeeNetModel(this); + } + } +} diff --git a/coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributor.java b/coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributor.java new file mode 100644 index 00000000..4b0950c2 --- /dev/null +++ b/coffeenet-platform-domain/src/main/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributor.java @@ -0,0 +1,17 @@ +package rocks.coffeenet.platform.domain.web; + +/** + * Contributes additional properties to the global COLA view model. + * + * @author Florian 'punycode' Krupicka - zh@punyco.de + */ +@FunctionalInterface +public interface CoffeeNetModelContributor { + + /** + * Contribute parameters. + * + * @param builder + */ + void contribute(CoffeeNetModel.Builder builder); +} diff --git a/coffeenet-platform-domain/src/test/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributorTests.java b/coffeenet-platform-domain/src/test/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributorTests.java new file mode 100644 index 00000000..c4600406 --- /dev/null +++ b/coffeenet-platform-domain/src/test/java/rocks/coffeenet/platform/domain/web/CoffeeNetModelContributorTests.java @@ -0,0 +1,86 @@ +package rocks.coffeenet.platform.domain.web; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + + +/** + * @author Florian 'punycode' Krupicka - zh@punyco.de + */ +class CoffeeNetModelContributorTests { + + private CoffeeNetModel.Builder builder; + + @BeforeEach + void setup() { + + builder = new CoffeeNetModel.Builder(); + } + + + @Test + @DisplayName("should add singular detail from contributer") + void singularDetail() { + + CoffeeNetModelContributor c = (b) -> b.withDetail("single", "value"); + + c.contribute(builder); + + CoffeeNetModel model = builder.build(); + assertThat(model).containsExactly(entry("single", "value")); + } + + + @Test + @DisplayName("should add complex detail from contributer") + void complexDetail() { + + Map complex = Collections.singletonMap("key", "value"); + CoffeeNetModelContributor c = (b) -> b.withDetail("complex", complex); + + c.contribute(builder); + + CoffeeNetModel model = builder.build(); + assertThat(model).contains(entry("complex", complex)); + } + + + @Test + @DisplayName("should add details from multiple contributers") + void multipleContributors() { + + CoffeeNetModelContributor c1 = (b) -> b.withDetail("key1", "value1"); + CoffeeNetModelContributor c2 = (b) -> b.withDetail("key2", "value2"); + + c1.contribute(builder); + c2.contribute(builder); + + CoffeeNetModel model = builder.build(); + assertThat(model).contains(entry("key2", "value2"), entry("key1", "value1")); + } + + + @Test + @DisplayName("should add multiple details from contributer") + void multipleDetails() { + + HashMap map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + + CoffeeNetModelContributor c = (b) -> b.withDetails(map); + + c.contribute(builder); + + CoffeeNetModel model = builder.build(); + assertThat(model).containsExactly(entry("key1", "value1"), entry("key2", "value2")); + } +}