From 7f0341087eb21ce7f9bde71e4b9a19a9843ec77e Mon Sep 17 00:00:00 2001 From: Rsl1122 <24460436+Rsl1122@users.noreply.github.com> Date: Tue, 11 Feb 2020 11:24:28 +0200 Subject: [PATCH] Implemented a CompositeResolver Allows building tree-like structure for resolution --- .../web/resolver/CompositeResolver.java | 101 ++++++++++++++++++ .../plan/delivery/web/resolver/Resolver.java | 2 +- .../web/resolver/ResponseBuilder.java | 8 +- .../plan/delivery/web/resolver/URLTarget.java | 15 +++ 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java new file mode 100644 index 000000000..1c82c69ed --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java @@ -0,0 +1,101 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Utility Resolver for organizing resolution in a tree-like structure. + *

+ * CompositeResolver removes first part of the target with {@link URLTarget#omitFirst()} + * before calling the child Resolvers. + * + * @author Rsl1122 + */ +public final class CompositeResolver implements Resolver { + + private final List prefixes; + private final List resolvers; + + CompositeResolver() { + this.prefixes = new ArrayList<>(); + this.resolvers = new ArrayList<>(); + } + + public static CompositeResolver.Builder builder() { + return new Builder(); + } + + private Optional getResolver(URLTarget target) { + return target.getPart(0).flatMap(this::find); + } + + private Optional find(String prefix) { + for (int i = 0; i < prefixes.size(); i++) { + if (prefixes.get(i).equals(prefix)) { + return Optional.of(resolvers.get(i)); + } + } + return Optional.empty(); + } + + void add(String prefix, Resolver resolver) { + if (prefix == null) throw new IllegalArgumentException("Prefix can not be null"); + if (resolver == null) throw new IllegalArgumentException("Resolver can not be null"); + prefixes.add(prefix); + resolvers.add(resolver); + } + + @Override + public boolean canAccess(WebUser permissions, URLTarget target, Parameters parameters) { + return getResolver(target) + .map(resolver -> resolver.canAccess(permissions, target.omitFirst(), parameters)) + .orElse(true); + } + + @Override + public Optional resolve(URLTarget target, Parameters parameters) { + return getResolver(target) + .flatMap(resolver -> resolver.resolve(target.omitFirst(), parameters)); + } + + public static class Builder { + private final CompositeResolver composite; + + private Builder() { + this.composite = new CompositeResolver(); + } + + /** + * Add a new resolver to the CompositeResolver. + * + * @param prefix Start of the target (first part of the target string, eg "example" in "/example/target/", or "" in "/") + * @param resolver Resolver to call for this target, {@link URLTarget#omitFirst()} will be called for Resolver method calls. + * @return this builder. + */ + public Builder add(String prefix, Resolver resolver) { + composite.add(prefix, resolver); + return this; + } + + public CompositeResolver build() { + return composite; + } + } +} \ No newline at end of file diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Resolver.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Resolver.java index fec439e73..5d452979f 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Resolver.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Resolver.java @@ -28,7 +28,7 @@ public interface Resolver { * @param permissions WebUser that is accessing this page. * @param target Target that is being accessed, /example/target * @param parameters Parameters in the URL, ?param=value etc. - * @return true if allowed, false if response should be 403 (forbidden) + * @return true if allowed or invalid target, false if response should be 403 (forbidden) */ boolean canAccess(WebUser permissions, URLTarget target, Parameters parameters); diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java index d6066b0e9..b557a2c2b 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java @@ -84,7 +84,7 @@ public class ResponseBuilder { } public ResponseBuilder setContent(String content, Charset charset) { - String mimeType = response.headers.get("Content-Type"); + String mimeType = getMimeType(); response.charset = charset; if (mimeType != null) { @@ -118,13 +118,17 @@ public class ResponseBuilder { public Response build() { byte[] content = response.bytes; exceptionIf(content == null, "Content not defined for Response"); - String mimeType = response.getHeaders().get("Content-Type"); + String mimeType = getMimeType(); exceptionIf(content.length > 0 && mimeType == null, "MIME Type not defined for Response"); exceptionIf(content.length > 0 && mimeType.isEmpty(), "MIME Type empty for Response"); exceptionIf(response.code < 100 || response.code >= 600, "HTTP Status code out of bounds (" + response.code + ")"); return response; } + private String getMimeType() { + return response.headers.get("Content-Type"); + } + private void exceptionIf(boolean value, String errorMsg) { if (value) throw new InvalidResponseException(errorMsg); } diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/URLTarget.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/URLTarget.java index ccaf54bb2..326f3f567 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/URLTarget.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/URLTarget.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.delivery.web.resolver; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -34,6 +35,7 @@ public final class URLTarget { private List parse(String target) { String[] partArray = target.split("/"); // Ignores index 0, assuming target starts with / + if (partArray.length == 1) return Collections.emptyList(); return Arrays.asList(partArray) .subList(1, partArray.length); } @@ -63,4 +65,17 @@ public final class URLTarget { public boolean endsWith(String suffix) { return full.endsWith(suffix); } + + /** + * Immutable modification, removes first part of the target string. + *

+ * Example: URLTarget "/example/target" return value of omitFirst URLTarget is "/target" + * Example: URLTarget "/example" return value of omitFirst URLTarget is "/" + * Example: URLTarget "/" return value of omitFirst URLTarget is "" + * + * @return new URLTarget with first part removed. + */ + public URLTarget omitFirst() { + return new URLTarget(full.replaceFirst("/" + getPart(0).orElse(""), "")); + } }