From a194e5db62e41678b089e6cfb0fe4cb285b4fbba Mon Sep 17 00:00:00 2001 From: Manuel Friedli <manuel@fritteli.ch> Date: Sat, 8 Apr 2023 09:35:10 +0200 Subject: [PATCH] Refactoring. --- pom.xml | 6 +- .../ch/fritteli/labyrinth/server/Main.java | 3 + .../fritteli/labyrinth/server/OutputType.java | 2 +- .../server/handler/CreateHandler.java | 106 +---------------- .../ParametersToLabyrinthExtractor.java | 111 ++++++++++++++++++ 5 files changed, 123 insertions(+), 105 deletions(-) create mode 100644 src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java diff --git a/pom.xml b/pom.xml index 39521da..668a742 100644 --- a/pom.xml +++ b/pom.xml @@ -20,9 +20,11 @@ <java.target.version>17</java.target.version> <jetbrains-annotations.version>24.0.1</jetbrains-annotations.version> <junit-jupiter.version>5.9.2</junit-jupiter.version> + <labyrinth-generator.version>0.0.3</labyrinth-generator.version> <logback.version>1.4.6</logback.version> <lombok.version>1.18.26</lombok.version> <slf4j.version>2.0.7</slf4j.version> + <undertow.version>2.3.5.Final</undertow.version> <vavr.version>0.10.4</vavr.version> </properties> @@ -46,7 +48,7 @@ <dependency> <groupId>ch.fritteli.labyrinth</groupId> <artifactId>labyrinth-generator</artifactId> - <version>0.0.3</version> + <version>${labyrinth-generator.version}</version> </dependency> <dependency> <groupId>io.vavr</groupId> @@ -73,7 +75,7 @@ <dependency> <groupId>io.undertow</groupId> <artifactId>undertow-core</artifactId> - <version>2.3.5.Final</version> + <version>${undertow.version}</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> diff --git a/src/main/java/ch/fritteli/labyrinth/server/Main.java b/src/main/java/ch/fritteli/labyrinth/server/Main.java index d8c5bfa..9800d8c 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/Main.java +++ b/src/main/java/ch/fritteli/labyrinth/server/Main.java @@ -1,9 +1,12 @@ package ch.fritteli.labyrinth.server; +import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; @Slf4j +@UtilityClass public class Main { + public static void main(String[] args) { LabyrinthServer.createAndStartServer() .onFailure(e -> log.error("Failed to create server. Stopping.", e)); diff --git a/src/main/java/ch/fritteli/labyrinth/server/OutputType.java b/src/main/java/ch/fritteli/labyrinth/server/OutputType.java index d40f668..4668bdd 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/OutputType.java +++ b/src/main/java/ch/fritteli/labyrinth/server/OutputType.java @@ -88,7 +88,7 @@ public enum OutputType { } @NonNull - public byte[] render(@NonNull final Labyrinth labyrinth) throws Exception { + public byte[] render(@NonNull final Labyrinth labyrinth) { return this.render.apply(labyrinth); } } diff --git a/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java b/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java index c5a304a..c1578fd 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java +++ b/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java @@ -6,33 +6,20 @@ import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; -import io.vavr.Tuple; import io.vavr.Tuple2; -import io.vavr.collection.HashMap; -import io.vavr.collection.HashMultimap; -import io.vavr.collection.HashSet; -import io.vavr.collection.List; -import io.vavr.collection.Multimap; -import io.vavr.collection.Set; -import io.vavr.collection.Stream; -import io.vavr.control.Option; import io.vavr.control.Try; -import lombok.Getter; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.Nullable; -import org.slf4j.MDC; - import java.nio.ByteBuffer; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Deque; import java.util.Map; -import java.util.Random; -import java.util.function.Function; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; @Slf4j public class CreateHandler extends AbstractHttpHandler { + public static final String PATH_TEMPLATE = "/create/{output}"; @Override @@ -77,89 +64,4 @@ public class CreateHandler extends AbstractHttpHandler { private Try<Tuple2<OutputType, Labyrinth>> createLabyrinthFromRequestParameters(final Map<String, Deque<String>> queryParameters) { return new ParametersToLabyrinthExtractor(queryParameters).createLabyrinth(); } - - private enum RequestParameter { - WIDTH(p -> Try.of(() -> Integer.parseInt(p)).toOption(), "w", "width"), - HEIGHT(p -> Try.of(() -> Integer.parseInt(p)).toOption(), "h", "height"), - ID(p -> Try.of(() -> Long.parseLong(p)).toOption(), "i", "id"), - OUTPUT(OutputType::ofString, "o", "output"); - @NonNull - private final Function<String, Option<?>> extractor; - @Getter - @NonNull - private final Set<String> names; - - RequestParameter(@NonNull final String... names) { - this.extractor = null; - this.names = HashSet.of(names); - } - - RequestParameter(@NonNull final Function<String, Option<?>> extractor, @NonNull final String... names) { - this.extractor = extractor; - this.names = HashSet.of(names); - } - - static Option<RequestParameter> parseName(@Nullable final String name) { - if (name == null) { - return Option.none(); - } - return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase)); - } - - @NonNull <T> Option<T> extractParameterValue(@NonNull final String parameter) { - return (Option<T>) this.extractor.apply(parameter); - } - } - - private static class ParametersToLabyrinthExtractor { - @NonNull - private final Multimap<String, String> queryParameters; - - ParametersToLabyrinthExtractor(@NonNull final Map<String, Deque<String>> queryParameters) { - this.queryParameters = HashMap.ofAll(queryParameters).foldLeft(HashMultimap.<String>withSet().empty(), (map, tuple) -> { - final String key = tuple._1(); - return Stream.ofAll(tuple._2()).foldLeft(map, (m, value) -> m.put(key, value)); - }); - } - - @NonNull - Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() { - final Option<OutputType> output = getParameterValue(RequestParameter.OUTPUT); - final Option<Integer> width = getParameterValue(RequestParameter.WIDTH); - final Option<Integer> height = getParameterValue(RequestParameter.HEIGHT); - final Option<Long> id = getParameterValue(RequestParameter.ID); - - if (output.isEmpty()) { - return Try.failure(new IllegalArgumentException("Path parameter %s is required and must be one of: %s".formatted( - RequestParameter.OUTPUT.getNames().mkString("'", " / ", "'"), - Stream.of(OutputType.values()) - .flatMap(OutputType::getNames) - .mkString(", ") - ))); - } - if (width.isEmpty()) { - return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted( - RequestParameter.WIDTH.getNames().mkString("'", " / ", "'") - ))); - } - if (height.isEmpty()) { - return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted( - RequestParameter.HEIGHT.getNames().mkString("'", " / ", "'") - ))); - } - - return Try.of(() -> Tuple.of(output.get(), new Labyrinth(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong())))); - } - - @NonNull - private <T> Option<T> getParameterValue(@NonNull final RequestParameter parameter) { - return this.getParameterValues(parameter) - .foldLeft(Option.none(), (type, param) -> type.orElse(() -> parameter.extractParameterValue(param))); - } - - @NonNull - private Stream<String> getParameterValues(@NonNull final RequestParameter parameter) { - return parameter.names.toStream().flatMap(name -> Stream.ofAll(this.queryParameters.getOrElse(name, List.empty()))); - } - } } diff --git a/src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java b/src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java new file mode 100644 index 0000000..b766b77 --- /dev/null +++ b/src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java @@ -0,0 +1,111 @@ +package ch.fritteli.labyrinth.server.handler; + +import ch.fritteli.labyrinth.generator.model.Labyrinth; +import ch.fritteli.labyrinth.server.OutputType; +import io.vavr.Tuple; +import io.vavr.Tuple2; +import io.vavr.collection.HashMap; +import io.vavr.collection.HashMultimap; +import io.vavr.collection.HashSet; +import io.vavr.collection.List; +import io.vavr.collection.Multimap; +import io.vavr.collection.Set; +import io.vavr.collection.Stream; +import io.vavr.control.Option; +import io.vavr.control.Try; +import java.util.Deque; +import java.util.Map; +import java.util.Random; +import java.util.function.Function; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; + +class ParametersToLabyrinthExtractor { + + @NonNull + private final Multimap<RequestParameter, ?> queryParameters; + + ParametersToLabyrinthExtractor(@NonNull final Map<String, Deque<String>> queryParameters) { + this.queryParameters = HashMap.ofAll(queryParameters) + .foldLeft( + HashMultimap.withSet().empty(), + (map, tuple) -> RequestParameter.parseName(tuple._1()).map(parameter -> Stream.ofAll(tuple._2()) + .flatMap(parameter::extractParameterValue) + .foldLeft(map, (m, value) -> m.put(parameter, value))) + .getOrElse(map) + ); + } + + @NonNull + Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() { + final Option<OutputType> output = getParameterValue(RequestParameter.OUTPUT); + final Option<Integer> width = getParameterValue(RequestParameter.WIDTH); + final Option<Integer> height = getParameterValue(RequestParameter.HEIGHT); + final Option<Long> id = getParameterValue(RequestParameter.ID); + + if (output.isEmpty()) { + return Try.failure(new IllegalArgumentException("Path parameter %s is required and must be one of: %s".formatted( + RequestParameter.OUTPUT.getNames().mkString("'", " / ", "'"), + Stream.of(OutputType.values()) + .flatMap(OutputType::getNames) + .mkString(", ") + ))); + } + if (width.isEmpty()) { + return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted( + RequestParameter.WIDTH.getNames().mkString("'", " / ", "'") + ))); + } + if (height.isEmpty()) { + return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted( + RequestParameter.HEIGHT.getNames().mkString("'", " / ", "'") + ))); + } + + return Try.of(() -> Tuple.of(output.get(), new Labyrinth(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong())))); + } + + @NonNull + private <T> Option<T> getParameterValue(@NonNull final RequestParameter parameter) { + return (Option<T>) this.queryParameters.getOrElse(parameter, List.empty()) + .headOption(); + } + + @Slf4j + private enum RequestParameter { + WIDTH(p -> Try.of(() -> Integer.parseInt(p)) + .toOption() + .onEmpty(() -> log.debug("Unparseable value for parameter 'width': '{}'", p)), "w", "width"), + HEIGHT(p -> Try.of(() -> Integer.parseInt(p)) + .toOption() + .onEmpty(() -> log.debug("Unparseable value for parameter 'height': '{}'", p)), "h", "height"), + ID(p -> Try.of(() -> Long.parseLong(p)) + .toOption() + .onEmpty(() -> log.debug("Unparseable value for parameter 'id': '{}'", p)), "i", "id"), + OUTPUT(p -> OutputType.ofString(p) + .onEmpty(() -> log.debug("Unparseable value for parameter 'output': '{}'", p)), "o", "output"); + @NonNull + private final Function<String, Option<?>> extractor; + @Getter + @NonNull + private final Set<String> names; + + RequestParameter(@NonNull final Function<String, Option<?>> extractor, @NonNull final String... names) { + this.extractor = extractor; + this.names = HashSet.of(names); + } + + static Option<RequestParameter> parseName(@Nullable final String name) { + if (name == null) { + return Option.none(); + } + return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase)); + } + + @NonNull Option<?> extractParameterValue(@NonNull final String parameter) { + return this.extractor.apply(parameter); + } + } +}