From 6c2892ef2f5ac4b8a4a3a67f0c2942788b9235a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=A2=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D1=85=D0=BE=D0=B2?= Date: Wed, 24 Jul 2024 09:32:10 +0400 Subject: [PATCH] refactor(server): moved ServerContext creation to factory Refs: #43 --- .../ru/ewc/checklogic/FullServerContext.java | 154 ++++++++++++++++++ .../java/ru/ewc/checklogic/LogicChecker.java | 2 +- .../java/ru/ewc/checklogic/ServerContext.java | 121 ++------------ .../ewc/checklogic/ServerContextFactory.java | 58 +++++++ .../ru/ewc/checklogic/server/CommandPage.java | 2 +- .../checklogic/server/ResultOfTestsPage.java | 8 +- 6 files changed, 231 insertions(+), 114 deletions(-) create mode 100644 src/main/java/ru/ewc/checklogic/FullServerContext.java create mode 100644 src/main/java/ru/ewc/checklogic/ServerContextFactory.java diff --git a/src/main/java/ru/ewc/checklogic/FullServerContext.java b/src/main/java/ru/ewc/checklogic/FullServerContext.java new file mode 100644 index 0000000..cb2f1bc --- /dev/null +++ b/src/main/java/ru/ewc/checklogic/FullServerContext.java @@ -0,0 +1,154 @@ +/* + * MIT License + * + * Copyright (c) 2024 Decision-Driven Development + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package ru.ewc.checklogic; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.SneakyThrows; +import ru.ewc.decisions.api.ComputationContext; +import ru.ewc.decisions.api.DecitaException; +import ru.ewc.state.State; + +/** + * I am a unique instance of a decision table computation. + * + * @since 0.1.0 + */ +@SuppressWarnings("PMD.ProhibitPublicStaticMethods") +public final class FullServerContext implements ServerContext { + /** + * The stored state of the system, persisted between requests. + */ + private final State state; + + /** + * The URI of the tables folder, used to recreate the computation context for each request. + */ + private final URI tables; + + /** + * The URI of the commands folder, used to recreate the computation context for each request. + */ + private final URI commands; + + /** + * The context of the computation. + */ + private ComputationContext context; + + /** + * The cached request parameters. + */ + private final Map parameters; + + FullServerContext(final State initial, final URI tables, final URI commands) { + this.state = initial; + this.tables = tables; + this.commands = commands; + this.context = new ComputationContext(initial, tables, commands); + this.parameters = new HashMap<>(2); + } + + @Override + public void perform(final String command) { + this.perform(command, Map.of()); + } + + @Override + public void perform(final String command, final Map args) { + args.forEach( + (key, value) -> { + if (!value.isEmpty()) { + final String[] split = key.split("::"); + this.context.setValueFor(split[0], split[1], value); + } + }); + this.context.perform(command); + this.context = new ComputationContext(this.state, this.tables, this.commands); + } + + @Override + public Map stateFor(final String table, final Map entities) { + final Map actual = HashMap.newHashMap(entities.size()); + for (final String fragment : entities.keySet()) { + actual.put(fragment, this.context.valueFor(table, fragment)); + } + return actual; + } + + @Override + public Map> storedState() { + return this.context.storedState(); + } + + @Override + public String valueFor(final String locator, final String fragment) { + String value; + try { + value = this.context.valueFor(locator, fragment); + } catch (final DecitaException exception) { + value = ""; + } + return value; + } + + @Override + public Map> commandData() { + return this.context.commandData(); + } + + @Override + public boolean isAvailable(final String command, final String field) { + return "true".equalsIgnoreCase(this.context.decisionFor(command).get(field)); + } + + @Override + public void update(final List values) { + this.state.locators().put(this.cached("request"), InMemoryStorage.from(values)); + this.context = new ComputationContext(this.state, this.tables, this.commands); + } + + @Override + public String cached(final String parameter) { + return this.parameters.getOrDefault(parameter, ""); + } + + @Override + public void cache(final String parameter, final String value) { + this.parameters.put(parameter, value); + } + + @SneakyThrows + static ServerContext contextForFolder(final String root) { + final ServerContext result = new ServerContextFactory(root).instance( + LogicChecker.stateFromAppConfig(FileUtils.applicationConfig(root)) + ); + result.cache("available", "available"); + result.cache("request", "request"); + return result; + } +} diff --git a/src/main/java/ru/ewc/checklogic/LogicChecker.java b/src/main/java/ru/ewc/checklogic/LogicChecker.java index faca196..883a143 100644 --- a/src/main/java/ru/ewc/checklogic/LogicChecker.java +++ b/src/main/java/ru/ewc/checklogic/LogicChecker.java @@ -52,7 +52,7 @@ public static void main(final String[] args) { throw new IllegalArgumentException("Please provide the path to the resources"); } final String root = args[0]; - new WebServer(ServerContext.contextForFolder(root), root).start(); + new WebServer(FullServerContext.contextForFolder(root), root).start(); } @SneakyThrows diff --git a/src/main/java/ru/ewc/checklogic/ServerContext.java b/src/main/java/ru/ewc/checklogic/ServerContext.java index ec40d2d..8970337 100644 --- a/src/main/java/ru/ewc/checklogic/ServerContext.java +++ b/src/main/java/ru/ewc/checklogic/ServerContext.java @@ -21,128 +21,35 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package ru.ewc.checklogic; -import java.net.URI; -import java.nio.file.Path; -import java.util.HashMap; import java.util.List; import java.util.Map; -import lombok.SneakyThrows; -import ru.ewc.decisions.api.ComputationContext; -import ru.ewc.decisions.api.DecitaException; -import ru.ewc.state.State; /** - * I am a unique instance of a decision table computation. + * I am the context for the server. I provide the server with the necessary information to perform + * the commands, to store the state of the system and to make decisions based on the state. * - * @since 0.1.0 + * @since 0.3.2 */ -@SuppressWarnings("PMD.ProhibitPublicStaticMethods") -public final class ServerContext { - /** - * The stored state of the system, persisted between requests. - */ - private final State state; - - /** - * The URI of the tables folder, used to recreate the computation context for each request. - */ - private final URI tables; - - /** - * The URI of the commands folder, used to recreate the computation context for each request. - */ - private final URI commands; - - /** - * The context of the computation. - */ - private ComputationContext context; - - /** - * The cached request parameters. - */ - private final Map parameters; - - public ServerContext(final State initial, final URI tables, final URI commands) { - this.state = initial; - this.tables = tables; - this.commands = commands; - this.context = new ComputationContext(initial, tables, commands); - this.parameters = new HashMap<>(2); - } - - public void perform(final String command) { - this.perform(command, Map.of()); - } - - public void perform(final String command, final Map args) { - args.forEach( - (key, value) -> { - if (!value.isEmpty()) { - final String[] split = key.split("::"); - this.context.setValueFor(split[0], split[1], value); - } - }); - this.context.perform(command); - this.context = new ComputationContext(this.state, this.tables, this.commands); - } +public interface ServerContext { + void perform(String command); - public Map stateFor(final String table, final Map entities) { - final Map actual = HashMap.newHashMap(entities.size()); - for (final String fragment : entities.keySet()) { - actual.put(fragment, this.context.valueFor(table, fragment)); - } - return actual; - } + void perform(String command, Map args); - public Map> storedState() { - return this.context.storedState(); - } + Map stateFor(String table, Map entities); - public String valueFor(final String locator, final String fragment) { - String value; - try { - value = this.context.valueFor(locator, fragment); - } catch (final DecitaException exception) { - value = ""; - } - return value; - } + Map> storedState(); - public Map> commandData() { - return this.context.commandData(); - } + String valueFor(String locator, String fragment); - public boolean isAvailable(final String command, final String field) { - return "true".equalsIgnoreCase(this.context.decisionFor(command).get(field)); - } + Map> commandData(); - public void update(final List values) { - this.state.locators().put(this.cached("request"), InMemoryStorage.from(values)); - this.context = new ComputationContext(this.state, this.tables, this.commands); - } + boolean isAvailable(String command, String field); - public String cached(final String parameter) { - return this.parameters.getOrDefault(parameter, ""); - } + void update(List values); - public void cache(final String parameter, final String value) { - this.parameters.put(parameter, value); - } + String cached(String parameter); - @SneakyThrows - // @todo #33 Move server context creation to a factory - static ServerContext contextForFolder(final String root) { - final ServerContext result = new ServerContext( - LogicChecker.stateFromAppConfig(FileUtils.applicationConfig(root)), - Path.of(root, "tables").toUri(), - Path.of(root, "commands").toUri() - ); - result.cache("available", "available"); - result.cache("request", "request"); - return result; - } + void cache(String parameter, String value); } diff --git a/src/main/java/ru/ewc/checklogic/ServerContextFactory.java b/src/main/java/ru/ewc/checklogic/ServerContextFactory.java new file mode 100644 index 0000000..dc8c349 --- /dev/null +++ b/src/main/java/ru/ewc/checklogic/ServerContextFactory.java @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2024 Decision-Driven Development + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package ru.ewc.checklogic; + +import java.nio.file.Path; +import ru.ewc.state.State; + +/** + * I am a factory for creating server contexts. + * + * @since 0.3.2 + */ +public class ServerContextFactory { + /** + * The root path for the external business logic resources. + */ + private final String root; + + public ServerContextFactory(final String root) { + this.root = root; + } + + /** + * Creates a new server context. + * + * @param initial The initial state of the system. + * @return A new server context. + */ + // @todo #33 Decide on what type of ServerContext to create + public ServerContext instance(final State initial) { + return new FullServerContext( + initial, + Path.of(this.root, "tables").toUri(), + Path.of(this.root, "commands").toUri() + ); + } +} diff --git a/src/main/java/ru/ewc/checklogic/server/CommandPage.java b/src/main/java/ru/ewc/checklogic/server/CommandPage.java index e183877..bc4e36f 100644 --- a/src/main/java/ru/ewc/checklogic/server/CommandPage.java +++ b/src/main/java/ru/ewc/checklogic/server/CommandPage.java @@ -83,7 +83,7 @@ public Response executeCommand(final Request request) { Response response; try { this.computation.perform(command, args); - response = Response.htmlOk("OK", Map.of("HX-Redirect", "/")); + response = Response.htmlOk("OK", Map.of("HX-Redirect", "/state")); } catch (final DecitaException exception) { response = Response.htmlOk( this.error.renderTemplate( diff --git a/src/main/java/ru/ewc/checklogic/server/ResultOfTestsPage.java b/src/main/java/ru/ewc/checklogic/server/ResultOfTestsPage.java index df17501..aa2f265 100644 --- a/src/main/java/ru/ewc/checklogic/server/ResultOfTestsPage.java +++ b/src/main/java/ru/ewc/checklogic/server/ResultOfTestsPage.java @@ -31,7 +31,6 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +42,7 @@ import ru.ewc.checklogic.FileUtils; import ru.ewc.checklogic.InMemoryStorage; import ru.ewc.checklogic.ServerContext; +import ru.ewc.checklogic.ServerContextFactory; import ru.ewc.checklogic.TestData; import ru.ewc.checklogic.TestResult; import ru.ewc.decisions.api.Locator; @@ -105,10 +105,8 @@ private Response testPage(final Request request) { @SneakyThrows private TestResult performTest(final TestData test) { final SoftAssertions softly = new SoftAssertions(); - final ServerContext target = new ServerContext( - stateFromFile(Files.newInputStream(new File(test.file()).toPath()), this.root), - Path.of(this.root, "tables").toUri(), - Path.of(this.root, "commands").toUri() + final ServerContext target = new ServerContextFactory(this.root).instance( + stateFromFile(Files.newInputStream(new File(test.file()).toPath()), this.root) ); TestResult result; try {