From 8ec4bd7584ad67b4aebd2a88a1e33902923a5375 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 18 May 2022 13:36:14 +0200 Subject: refactor(web/ui): Remove module nesting in Quarkus extension This change updates the OpenDC web UI Quarkus extension to live completely in the `opendc-web` directory, as opposed to adding another level of nesting. This also allows us to properly name the artifacts of the Quarkus extension modules. --- opendc-web/opendc-web-api/build.gradle.kts | 4 +- .../build.gradle.kts | 39 +++ .../web/ui/deployment/AuthConfiguration.java | 52 ++++ .../opendc/web/ui/deployment/OpenDCUiConfig.java | 63 +++++ .../web/ui/deployment/OpenDCUiProcessor.java | 314 +++++++++++++++++++++ .../ui/deployment/OpenDCUiRoutingBuildItem.java | 119 ++++++++ opendc-web/opendc-web-ui-quarkus/build.gradle.kts | 19 ++ .../deployment/build.gradle.kts | 39 --- .../web/ui/deployment/AuthConfiguration.java | 52 ---- .../opendc/web/ui/deployment/OpenDCUiConfig.java | 63 ----- .../web/ui/deployment/OpenDCUiProcessor.java | 314 --------------------- .../ui/deployment/OpenDCUiRoutingBuildItem.java | 119 -------- .../opendc-web-ui-quarkus/runtime/build.gradle.kts | 36 --- .../opendc/web/ui/runtime/OpenDCUiRecorder.java | 107 ------- .../web/ui/runtime/OpenDCUiRuntimeConfig.java | 39 --- .../main/resources/META-INF/quarkus-extension.yaml | 5 - .../opendc/web/ui/runtime/OpenDCUiRecorder.java | 107 +++++++ .../web/ui/runtime/OpenDCUiRuntimeConfig.java | 39 +++ .../main/resources/META-INF/quarkus-extension.yaml | 5 + settings.gradle.kts | 4 +- 20 files changed, 761 insertions(+), 778 deletions(-) create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java create mode 100644 opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java create mode 100644 opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/opendc-web/opendc-web-api/build.gradle.kts b/opendc-web/opendc-web-api/build.gradle.kts index 5ef6009f..fe493f1d 100644 --- a/opendc-web/opendc-web-api/build.gradle.kts +++ b/opendc-web/opendc-web-api/build.gradle.kts @@ -31,8 +31,8 @@ dependencies { implementation(enforcedPlatform(libs.quarkus.bom)) implementation(projects.opendcWeb.opendcWebProto) - compileOnly(projects.opendcWeb.opendcWebUiQuarkus.deployment) /* Temporary fix for Quarkus/Gradle issues */ - implementation(projects.opendcWeb.opendcWebUiQuarkus.runtime) + compileOnly(projects.opendcWeb.opendcWebUiQuarkusDeployment) /* Temporary fix for Quarkus/Gradle issues */ + implementation(projects.opendcWeb.opendcWebUiQuarkus) implementation(libs.quarkus.kotlin) implementation(libs.quarkus.resteasy.core) diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts new file mode 100644 index 00000000..5a42aaea --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * 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. + */ + +description = "Quarkus extension for serving OpenDC web interface" + +/* Build configuration */ +plugins { + `java-library-conventions` +} + +dependencies { + implementation(platform(libs.quarkus.bom)) + + implementation(projects.opendcWeb.opendcWebUi) + implementation(projects.opendcWeb.opendcWebUiQuarkus) + + implementation(libs.quarkus.core.deployment) + implementation(libs.quarkus.vertx.http.deployment) + implementation(libs.quarkus.arc.deployment) +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java new file mode 100644 index 00000000..2e4d9198 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * 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 org.opendc.web.ui.deployment; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +import java.util.Optional; + +/** + * Auth configuration for the OpenDC UI extension. + */ +@ConfigGroup +public class AuthConfiguration { + /** + * The authentication domain. + */ + @ConfigItem + Optional domain; + + /** + * The client identifier used by the OpenDC web ui. + */ + @ConfigItem + Optional clientId; + + /** + * The audience of the OpenDC API. + */ + @ConfigItem + Optional audience; +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java new file mode 100644 index 00000000..50c1fbe3 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * 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 org.opendc.web.ui.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +import java.util.Optional; + +/** + * Build-time configuration for the OpenDC UI extension. + */ +@ConfigRoot(name = "opendc-ui") +public class OpenDCUiConfig { + /** + * A flag to include the OpenDC UI extension into the build. + */ + @ConfigItem(defaultValue = "true") + boolean include; + + /** + * The path where the OpenDC UI is available. + */ + @ConfigItem(defaultValue = "/") + String path; + + /** + * The base URL of the OpenDC API. + */ + @ConfigItem(defaultValue = "/api") + String apiBaseUrl; + + /** + * Configuration properties for web UI authentication. + */ + AuthConfiguration auth; + + /** + * Sentry DSN. + */ + @ConfigItem + Optional sentryDsn; +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java new file mode 100644 index 00000000..54782ace --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * 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 org.opendc.web.ui.deployment; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.maven.dependency.GACT; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathVisit; +import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; +import io.quarkus.vertx.http.deployment.RouteBuildItem; +import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; +import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; +import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; +import org.opendc.web.ui.runtime.OpenDCUiRecorder; +import org.opendc.web.ui.runtime.OpenDCUiRuntimeConfig; + +import java.io.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Build processor for the OpenDC web UI Quarkus extension. + */ +public class OpenDCUiProcessor { + + private static final String FEATURE = "opendc-ui"; + private static final GACT OPENDC_UI_WEBJAR_ARTIFACT_KEY = new GACT("org.opendc.web", "opendc-web-ui", null, "jar"); + private static final String OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH = "META-INF/resources/opendc-web-ui"; + private static final Pattern PATH_PARAM_PATTERN = Pattern.compile("\\[(\\w+)]"); + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Provide the {@link FeatureBuildItem} for this Quarkus extension. + */ + @BuildStep(onlyIf = IsIncluded.class) + public FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + /** + * Build the WebJar that is used to serve the Next.js resources. + */ + @BuildStep(onlyIf = IsIncluded.class) + public WebJarBuildItem buildWebJar(OpenDCUiConfig config, + HttpRootPathBuildItem httpRootPathBuildItem) { + return WebJarBuildItem.builder() + .artifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY) + .root(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH) + .onlyCopyNonArtifactFiles(false) + .useDefaultQuarkusBranding(false) + .filter(new InsertVariablesResourcesFilter(config, httpRootPathBuildItem)) + .build(); + } + + + /** + * Build the Next.js routes based on the route manifest generated by it. + */ + @BuildStep(onlyIf = IsIncluded.class) + public OpenDCUiRoutingBuildItem buildRoutes(CurateOutcomeBuildItem curateOutcomeBuildItem) throws IOException { + ResolvedDependency dependency = getAppArtifact(curateOutcomeBuildItem, OPENDC_UI_WEBJAR_ARTIFACT_KEY); + PathVisit visit = dependency.getContentTree().apply(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH + "/routes-manifest.json", v -> v); + + if (visit == null) { + throw new FileNotFoundException("Cannot find routes-manifest.json"); + } + + JsonNode routeManifest = objectMapper.readTree(visit.getUrl()); + + var pages = new ArrayList(); + for (Iterator it = routeManifest.get("staticRoutes").elements(); it.hasNext();) { + JsonNode route = it.next(); + + String page = route.get("page").asText(); + + // Static routes do not have any path parameters + pages.add(new OpenDCUiRoutingBuildItem.Page(page, page)); + } + + for (Iterator it = routeManifest.get("dynamicRoutes").elements(); it.hasNext();) { + JsonNode route = it.next(); + + String page = route.get("page").asText(); + String path = PATH_PARAM_PATTERN.matcher(page).replaceAll(r -> ":" + r.group(1)); + + pages.add(new OpenDCUiRoutingBuildItem.Page(path, page)); + } + + var redirects = new ArrayList(); + for (Iterator it = routeManifest.get("redirects").elements(); it.hasNext();) { + JsonNode redirect = it.next(); + if (redirect.has("internal")) { + continue; + } + + int statusCode = redirect.get("statusCode").asInt(); + String path = redirect.get("source").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); + String destination = redirect.get("destination").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); + + if (path.isEmpty()) { + path = "/"; + } + + redirects.add(new OpenDCUiRoutingBuildItem.Redirect(path, destination, statusCode)); + } + + var custom404 = routeManifest.get("pages404").asBoolean(); + return new OpenDCUiRoutingBuildItem(pages, redirects, custom404); + } + + /** + * Register the HTTP handles for the OpenDC web UI. + */ + @BuildStep(onlyIf = IsIncluded.class) + @Record(ExecutionTime.RUNTIME_INIT) + public void registerOpenDCUiHandler(OpenDCUiRecorder recorder, + BuildProducer routes, + HttpRootPathBuildItem httpRootPathBuildItem, + WebJarResultsBuildItem webJarResultsBuildItem, + OpenDCUiRoutingBuildItem openDCUiBuildItem, + OpenDCUiRuntimeConfig runtimeConfig, + OpenDCUiConfig buildConfig, + ShutdownContextBuildItem shutdownContext) { + + WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem.byArtifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY); + if (result == null) { + return; + } + + String basePath = httpRootPathBuildItem.resolvePath(buildConfig.path); + String finalDestination = result.getFinalDestination(); + + /* Construct dynamic routes */ + for (var redirect : openDCUiBuildItem.getRedirects()) { + String destination = basePath.equals("/") ? redirect.getDestination() : basePath + redirect.getDestination(); + + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(basePath + redirect.getPath()) + .handler(recorder.redirectHandler(destination, redirect.getStatusCode(), runtimeConfig)) + .build()); + } + + for (var page : openDCUiBuildItem.getPages()) { + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(basePath + page.getPath()) + .handler(recorder.pageHandler(finalDestination, page.getName(), runtimeConfig)) + .build()); + } + + /* Construct static routes */ + Handler staticHandler = recorder.staticHandler( + finalDestination, + basePath, + result.getWebRootConfigurations(), + runtimeConfig, + shutdownContext + ); + + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(buildConfig.path) + .displayOnNotFoundPage("OpenDC UI") + .routeConfigKey("quarkus.opendc-ui.path") + .handler(staticHandler) + .build()); + + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(buildConfig.path + "*") + .handler(staticHandler) + .build()); + } + + /** + * A {@link WebJarResourcesFilter} that instantiates the variables in the web jar resource. + */ + private static class InsertVariablesResourcesFilter implements WebJarResourcesFilter { + + private static final String HTML = ".html"; + private static final String CSS = ".css"; + private static final String JS = ".js"; + + private final OpenDCUiConfig config; + private final HttpRootPathBuildItem httpRootPathBuildItem; + + + public InsertVariablesResourcesFilter(OpenDCUiConfig config, HttpRootPathBuildItem httpRootPathBuildItem) { + this.config = config; + this.httpRootPathBuildItem = httpRootPathBuildItem; + } + + @Override + public FilterResult apply(String fileName, InputStream stream) throws IOException { + if (stream == null) { + return new FilterResult(null, false); + } + + // Allow replacement of variables in HTML, CSS, and JavaScript files + if (fileName.endsWith(HTML) || fileName.endsWith(CSS) || fileName.endsWith(JS)) { + byte[] oldContentBytes = stream.readAllBytes(); + String oldContents = new String(oldContentBytes); + String contents = substituteVariables(oldContents, this::substitute); + + boolean changed = contents.length() != oldContents.length() || !contents.equals(oldContents); + if (changed) { + return new FilterResult(new ByteArrayInputStream(contents.getBytes()), true); + } else { + return new FilterResult(new ByteArrayInputStream(oldContentBytes), false); + } + } + + return new FilterResult(stream, false); + } + + private String substitute(String var) { + switch (var) { + case "NEXT_BASE_PATH": + String basePath = httpRootPathBuildItem.resolvePath(config.path); + return basePath.equals("/") ? "" : basePath; // Base path must not end with trailing slash + case "NEXT_PUBLIC_API_BASE_URL": + return config.apiBaseUrl; + case "NEXT_PUBLIC_SENTRY_DSN": + return config.sentryDsn.orElse(""); + case "NEXT_PUBLIC_AUTH0_DOMAIN": + return config.auth.domain.orElse(""); + case "NEXT_PUBLIC_AUTH0_CLIENT_ID": + return config.auth.clientId.orElse(""); + case "NEXT_PUBLIC_AUTH0_AUDIENCE": + return config.auth.audience.orElse(""); + default: + return null; + } + } + + /** + * Pattern to match variables in the OpenDC web UI sources specified using the following format: "%%VAR_NAME%%". + *

+ * Be aware that to properly handle Next.js base path, we need to also match a possible forward slash in front + * of the variable. + */ + private static final Pattern PATTERN = Pattern.compile("/?%%(\\w+)%%"); + + /** + * Helper method to substitute variables in the OpenDC web UI. + */ + private static String substituteVariables(String contents, Function substitute) { + Matcher m = PATTERN.matcher(contents); + StringBuilder sb = new StringBuilder(); + + while (m.find()) { + String group = m.group(1); + String val = substitute.apply(group); + m.appendReplacement(sb, val != null ? val : group); + } + + m.appendTail(sb); + return sb.toString(); + } + } + + /** + * A {@link BooleanSupplier} to determine if the OpenDC web UI extension should be included. + */ + private static class IsIncluded implements BooleanSupplier { + OpenDCUiConfig config; + + @Override + public boolean getAsBoolean() { + return config.include; + } + } + + private static ResolvedDependency getAppArtifact(CurateOutcomeBuildItem curateOutcomeBuildItem, GACT artifactKey) { + for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getDependencies()) { + if (dep.getKey().equals(artifactKey)) { + return dep; + } + } + throw new RuntimeException("Could not find artifact " + artifactKey + " among the application dependencies"); + } +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java new file mode 100644 index 00000000..7e0f9408 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * 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 org.opendc.web.ui.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +import java.util.List; + +/** + * Build item containing the routes for the OpenDC web UI. + */ +public final class OpenDCUiRoutingBuildItem extends SimpleBuildItem { + + private final boolean custom404; + private final List pages; + private final List redirects; + + /** + * Construct a {@link OpenDCUiRoutingBuildItem} instance. + * + * @param routes The routes defined by Next.js. + * @param redirects The redirects that have been defined by Next.js. + * @param custom404 A flag to indicate that custom 404 pages are enabled. + */ + public OpenDCUiRoutingBuildItem(List routes, List redirects, boolean custom404) { + this.custom404 = custom404; + this.pages = routes; + this.redirects = redirects; + } + + public List getPages() { + return pages; + } + + public List getRedirects() { + return redirects; + } + + public boolean hasCustom404() { + return this.custom404; + } + + /** + * A redirect defined by the Next.js routes manifest. + */ + public static final class Redirect { + + private final String path; + private final String destination; + private final int statusCode; + + /** + * Construct a {@link Redirect} route. + * + * @param path The path that should result in a redirect. + * @param destination The destination of the redirect. + * @param statusCode The status code of the redirect. + */ + public Redirect(String path, String destination, int statusCode) { + this.statusCode = statusCode; + this.path = path; + this.destination = destination; + } + + public String getPath() { + return path; + } + + public String getDestination() { + return destination; + } + + public int getStatusCode() { + return statusCode; + } + } + + /** + * A page defined by the Next.js routes manifest. + */ + public static final class Page { + + private final String path; + private final String name; + + public Page(String path, String page) { + this.path = path; + this.name = page; + } + + public String getPath() { + return path; + } + + public String getName() { + return name; + } + } +} diff --git a/opendc-web/opendc-web-ui-quarkus/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus/build.gradle.kts index cbec021c..7f2fad20 100644 --- a/opendc-web/opendc-web-ui-quarkus/build.gradle.kts +++ b/opendc-web/opendc-web-ui-quarkus/build.gradle.kts @@ -21,3 +21,22 @@ */ description = "Quarkus extension for serving OpenDC web interface" + +plugins { + `java-library-conventions` + id("io.quarkus.extension") +} + +quarkusExtension { + deploymentModule = "opendc-web-ui-quarkus-deployment" +} + +dependencies { + implementation(platform(libs.quarkus.bom)) + + implementation(libs.quarkus.core.runtime) + implementation(libs.quarkus.vertx.http.runtime) + implementation(libs.quarkus.arc.runtime) +} + +evaluationDependsOn(projects.opendcWeb.opendcWebUiQuarkusDeployment.dependencyProject.path) diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts deleted file mode 100644 index 0f3ae8ce..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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. - */ - -description = "Quarkus extension for serving OpenDC web interface" - -/* Build configuration */ -plugins { - `java-library-conventions` -} - -dependencies { - implementation(enforcedPlatform(libs.quarkus.bom)) - - implementation(projects.opendcWeb.opendcWebUi) - implementation(projects.opendcWeb.opendcWebUiQuarkus.runtime) - - implementation(libs.quarkus.core.deployment) - implementation(libs.quarkus.vertx.http.deployment) - implementation(libs.quarkus.arc.deployment) -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java deleted file mode 100644 index 2e4d9198..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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 org.opendc.web.ui.deployment; - -import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; - -import java.util.Optional; - -/** - * Auth configuration for the OpenDC UI extension. - */ -@ConfigGroup -public class AuthConfiguration { - /** - * The authentication domain. - */ - @ConfigItem - Optional domain; - - /** - * The client identifier used by the OpenDC web ui. - */ - @ConfigItem - Optional clientId; - - /** - * The audience of the OpenDC API. - */ - @ConfigItem - Optional audience; -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java deleted file mode 100644 index 50c1fbe3..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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 org.opendc.web.ui.deployment; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigRoot; - -import java.util.Optional; - -/** - * Build-time configuration for the OpenDC UI extension. - */ -@ConfigRoot(name = "opendc-ui") -public class OpenDCUiConfig { - /** - * A flag to include the OpenDC UI extension into the build. - */ - @ConfigItem(defaultValue = "true") - boolean include; - - /** - * The path where the OpenDC UI is available. - */ - @ConfigItem(defaultValue = "/") - String path; - - /** - * The base URL of the OpenDC API. - */ - @ConfigItem(defaultValue = "/api") - String apiBaseUrl; - - /** - * Configuration properties for web UI authentication. - */ - AuthConfiguration auth; - - /** - * Sentry DSN. - */ - @ConfigItem - Optional sentryDsn; -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java deleted file mode 100644 index 54782ace..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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 org.opendc.web.ui.deployment; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.ExecutionTime; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.ShutdownContextBuildItem; -import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.maven.dependency.GACT; -import io.quarkus.maven.dependency.ResolvedDependency; -import io.quarkus.paths.PathVisit; -import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; -import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; -import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; -import io.vertx.core.Handler; -import io.vertx.ext.web.RoutingContext; -import org.opendc.web.ui.runtime.OpenDCUiRecorder; -import org.opendc.web.ui.runtime.OpenDCUiRuntimeConfig; - -import java.io.*; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.function.BooleanSupplier; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Build processor for the OpenDC web UI Quarkus extension. - */ -public class OpenDCUiProcessor { - - private static final String FEATURE = "opendc-ui"; - private static final GACT OPENDC_UI_WEBJAR_ARTIFACT_KEY = new GACT("org.opendc.web", "opendc-web-ui", null, "jar"); - private static final String OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH = "META-INF/resources/opendc-web-ui"; - private static final Pattern PATH_PARAM_PATTERN = Pattern.compile("\\[(\\w+)]"); - - private final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * Provide the {@link FeatureBuildItem} for this Quarkus extension. - */ - @BuildStep(onlyIf = IsIncluded.class) - public FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); - } - - /** - * Build the WebJar that is used to serve the Next.js resources. - */ - @BuildStep(onlyIf = IsIncluded.class) - public WebJarBuildItem buildWebJar(OpenDCUiConfig config, - HttpRootPathBuildItem httpRootPathBuildItem) { - return WebJarBuildItem.builder() - .artifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY) - .root(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH) - .onlyCopyNonArtifactFiles(false) - .useDefaultQuarkusBranding(false) - .filter(new InsertVariablesResourcesFilter(config, httpRootPathBuildItem)) - .build(); - } - - - /** - * Build the Next.js routes based on the route manifest generated by it. - */ - @BuildStep(onlyIf = IsIncluded.class) - public OpenDCUiRoutingBuildItem buildRoutes(CurateOutcomeBuildItem curateOutcomeBuildItem) throws IOException { - ResolvedDependency dependency = getAppArtifact(curateOutcomeBuildItem, OPENDC_UI_WEBJAR_ARTIFACT_KEY); - PathVisit visit = dependency.getContentTree().apply(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH + "/routes-manifest.json", v -> v); - - if (visit == null) { - throw new FileNotFoundException("Cannot find routes-manifest.json"); - } - - JsonNode routeManifest = objectMapper.readTree(visit.getUrl()); - - var pages = new ArrayList(); - for (Iterator it = routeManifest.get("staticRoutes").elements(); it.hasNext();) { - JsonNode route = it.next(); - - String page = route.get("page").asText(); - - // Static routes do not have any path parameters - pages.add(new OpenDCUiRoutingBuildItem.Page(page, page)); - } - - for (Iterator it = routeManifest.get("dynamicRoutes").elements(); it.hasNext();) { - JsonNode route = it.next(); - - String page = route.get("page").asText(); - String path = PATH_PARAM_PATTERN.matcher(page).replaceAll(r -> ":" + r.group(1)); - - pages.add(new OpenDCUiRoutingBuildItem.Page(path, page)); - } - - var redirects = new ArrayList(); - for (Iterator it = routeManifest.get("redirects").elements(); it.hasNext();) { - JsonNode redirect = it.next(); - if (redirect.has("internal")) { - continue; - } - - int statusCode = redirect.get("statusCode").asInt(); - String path = redirect.get("source").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); - String destination = redirect.get("destination").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); - - if (path.isEmpty()) { - path = "/"; - } - - redirects.add(new OpenDCUiRoutingBuildItem.Redirect(path, destination, statusCode)); - } - - var custom404 = routeManifest.get("pages404").asBoolean(); - return new OpenDCUiRoutingBuildItem(pages, redirects, custom404); - } - - /** - * Register the HTTP handles for the OpenDC web UI. - */ - @BuildStep(onlyIf = IsIncluded.class) - @Record(ExecutionTime.RUNTIME_INIT) - public void registerOpenDCUiHandler(OpenDCUiRecorder recorder, - BuildProducer routes, - HttpRootPathBuildItem httpRootPathBuildItem, - WebJarResultsBuildItem webJarResultsBuildItem, - OpenDCUiRoutingBuildItem openDCUiBuildItem, - OpenDCUiRuntimeConfig runtimeConfig, - OpenDCUiConfig buildConfig, - ShutdownContextBuildItem shutdownContext) { - - WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem.byArtifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY); - if (result == null) { - return; - } - - String basePath = httpRootPathBuildItem.resolvePath(buildConfig.path); - String finalDestination = result.getFinalDestination(); - - /* Construct dynamic routes */ - for (var redirect : openDCUiBuildItem.getRedirects()) { - String destination = basePath.equals("/") ? redirect.getDestination() : basePath + redirect.getDestination(); - - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(basePath + redirect.getPath()) - .handler(recorder.redirectHandler(destination, redirect.getStatusCode(), runtimeConfig)) - .build()); - } - - for (var page : openDCUiBuildItem.getPages()) { - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(basePath + page.getPath()) - .handler(recorder.pageHandler(finalDestination, page.getName(), runtimeConfig)) - .build()); - } - - /* Construct static routes */ - Handler staticHandler = recorder.staticHandler( - finalDestination, - basePath, - result.getWebRootConfigurations(), - runtimeConfig, - shutdownContext - ); - - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(buildConfig.path) - .displayOnNotFoundPage("OpenDC UI") - .routeConfigKey("quarkus.opendc-ui.path") - .handler(staticHandler) - .build()); - - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(buildConfig.path + "*") - .handler(staticHandler) - .build()); - } - - /** - * A {@link WebJarResourcesFilter} that instantiates the variables in the web jar resource. - */ - private static class InsertVariablesResourcesFilter implements WebJarResourcesFilter { - - private static final String HTML = ".html"; - private static final String CSS = ".css"; - private static final String JS = ".js"; - - private final OpenDCUiConfig config; - private final HttpRootPathBuildItem httpRootPathBuildItem; - - - public InsertVariablesResourcesFilter(OpenDCUiConfig config, HttpRootPathBuildItem httpRootPathBuildItem) { - this.config = config; - this.httpRootPathBuildItem = httpRootPathBuildItem; - } - - @Override - public FilterResult apply(String fileName, InputStream stream) throws IOException { - if (stream == null) { - return new FilterResult(null, false); - } - - // Allow replacement of variables in HTML, CSS, and JavaScript files - if (fileName.endsWith(HTML) || fileName.endsWith(CSS) || fileName.endsWith(JS)) { - byte[] oldContentBytes = stream.readAllBytes(); - String oldContents = new String(oldContentBytes); - String contents = substituteVariables(oldContents, this::substitute); - - boolean changed = contents.length() != oldContents.length() || !contents.equals(oldContents); - if (changed) { - return new FilterResult(new ByteArrayInputStream(contents.getBytes()), true); - } else { - return new FilterResult(new ByteArrayInputStream(oldContentBytes), false); - } - } - - return new FilterResult(stream, false); - } - - private String substitute(String var) { - switch (var) { - case "NEXT_BASE_PATH": - String basePath = httpRootPathBuildItem.resolvePath(config.path); - return basePath.equals("/") ? "" : basePath; // Base path must not end with trailing slash - case "NEXT_PUBLIC_API_BASE_URL": - return config.apiBaseUrl; - case "NEXT_PUBLIC_SENTRY_DSN": - return config.sentryDsn.orElse(""); - case "NEXT_PUBLIC_AUTH0_DOMAIN": - return config.auth.domain.orElse(""); - case "NEXT_PUBLIC_AUTH0_CLIENT_ID": - return config.auth.clientId.orElse(""); - case "NEXT_PUBLIC_AUTH0_AUDIENCE": - return config.auth.audience.orElse(""); - default: - return null; - } - } - - /** - * Pattern to match variables in the OpenDC web UI sources specified using the following format: "%%VAR_NAME%%". - *

- * Be aware that to properly handle Next.js base path, we need to also match a possible forward slash in front - * of the variable. - */ - private static final Pattern PATTERN = Pattern.compile("/?%%(\\w+)%%"); - - /** - * Helper method to substitute variables in the OpenDC web UI. - */ - private static String substituteVariables(String contents, Function substitute) { - Matcher m = PATTERN.matcher(contents); - StringBuilder sb = new StringBuilder(); - - while (m.find()) { - String group = m.group(1); - String val = substitute.apply(group); - m.appendReplacement(sb, val != null ? val : group); - } - - m.appendTail(sb); - return sb.toString(); - } - } - - /** - * A {@link BooleanSupplier} to determine if the OpenDC web UI extension should be included. - */ - private static class IsIncluded implements BooleanSupplier { - OpenDCUiConfig config; - - @Override - public boolean getAsBoolean() { - return config.include; - } - } - - private static ResolvedDependency getAppArtifact(CurateOutcomeBuildItem curateOutcomeBuildItem, GACT artifactKey) { - for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getDependencies()) { - if (dep.getKey().equals(artifactKey)) { - return dep; - } - } - throw new RuntimeException("Could not find artifact " + artifactKey + " among the application dependencies"); - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java deleted file mode 100644 index 7e0f9408..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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 org.opendc.web.ui.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; - -import java.util.List; - -/** - * Build item containing the routes for the OpenDC web UI. - */ -public final class OpenDCUiRoutingBuildItem extends SimpleBuildItem { - - private final boolean custom404; - private final List pages; - private final List redirects; - - /** - * Construct a {@link OpenDCUiRoutingBuildItem} instance. - * - * @param routes The routes defined by Next.js. - * @param redirects The redirects that have been defined by Next.js. - * @param custom404 A flag to indicate that custom 404 pages are enabled. - */ - public OpenDCUiRoutingBuildItem(List routes, List redirects, boolean custom404) { - this.custom404 = custom404; - this.pages = routes; - this.redirects = redirects; - } - - public List getPages() { - return pages; - } - - public List getRedirects() { - return redirects; - } - - public boolean hasCustom404() { - return this.custom404; - } - - /** - * A redirect defined by the Next.js routes manifest. - */ - public static final class Redirect { - - private final String path; - private final String destination; - private final int statusCode; - - /** - * Construct a {@link Redirect} route. - * - * @param path The path that should result in a redirect. - * @param destination The destination of the redirect. - * @param statusCode The status code of the redirect. - */ - public Redirect(String path, String destination, int statusCode) { - this.statusCode = statusCode; - this.path = path; - this.destination = destination; - } - - public String getPath() { - return path; - } - - public String getDestination() { - return destination; - } - - public int getStatusCode() { - return statusCode; - } - } - - /** - * A page defined by the Next.js routes manifest. - */ - public static final class Page { - - private final String path; - private final String name; - - public Page(String path, String page) { - this.path = path; - this.name = page; - } - - public String getPath() { - return path; - } - - public String getName() { - return name; - } - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts deleted file mode 100644 index f4131f0b..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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. - */ - -description = "Quarkus extension for serving OpenDC web interface" - -plugins { - `java-library-conventions` - id("io.quarkus.extension") -} - -dependencies { - implementation(enforcedPlatform(libs.quarkus.bom)) - - implementation(libs.quarkus.core.runtime) - implementation(libs.quarkus.vertx.http.runtime) - implementation(libs.quarkus.arc.runtime) -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java b/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java deleted file mode 100644 index 026a9039..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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 org.opendc.web.ui.runtime; - -import io.quarkus.runtime.ShutdownContext; -import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.vertx.http.runtime.devmode.FileSystemStaticHandler; -import io.quarkus.vertx.http.runtime.webjar.WebJarNotFoundHandler; -import io.quarkus.vertx.http.runtime.webjar.WebJarStaticHandler; -import io.vertx.core.Handler; -import io.vertx.ext.web.RoutingContext; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Helper class for serving the OpenDC web interface. - */ -@Recorder -public class OpenDCUiRecorder { - /** - * Construct a {@link Handler} for serving a page of the OpenDC web interface. - */ - public Handler pageHandler( - String finalDestination, - String page, - OpenDCUiRuntimeConfig runtimeConfig - ) { - if (runtimeConfig.enable) { - String pageDirectory = finalDestination + "/pages"; - return (event) -> { - event.response() - .setStatusCode(200) - .sendFile(pageDirectory + page + ".html"); - }; - } - - return new WebJarNotFoundHandler(); - } - - /** - * Construct a {@link Handler} for handling redirects in the OpenDC web interface. - */ - public Handler redirectHandler( - String destination, - int statusCode, - OpenDCUiRuntimeConfig runtimeConfig - ) { - if (runtimeConfig.enable) { - return (event) -> { - String query = event.request().query(); - String fullDestination = query != null ? destination + "?" + query : destination; - - event.response() - .setStatusCode(statusCode) - .putHeader("Location", fullDestination) - .end(); - }; - } - - return new WebJarNotFoundHandler(); - } - - /** - * Construct a {@link Handler} for serving the static files of the OpenDC web interface. - */ - public Handler staticHandler( - String finalDestination, - String path, - List webRootConfigurations, - OpenDCUiRuntimeConfig runtimeConfig, - ShutdownContext shutdownContext - ) { - if (runtimeConfig.enable) { - var augmentedWebRootConfigurations = webRootConfigurations - .stream() - .map(c -> new FileSystemStaticHandler.StaticWebRootConfiguration(c.getFileSystem(), c.getWebRoot().isEmpty() ? "static" : c.getWebRoot() + "/static")) - .collect(Collectors.toList()); - - WebJarStaticHandler handler = new WebJarStaticHandler(finalDestination + "/static", path, augmentedWebRootConfigurations); - shutdownContext.addShutdownTask(new ShutdownContext.CloseRunnable(handler)); - return handler; - } - - return new WebJarNotFoundHandler(); - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java b/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java deleted file mode 100644 index 8ae3b6a2..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * 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 org.opendc.web.ui.runtime; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; - -/** - * Configuration for the OpenDC web UI. - */ -@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-ui") -public class OpenDCUiRuntimeConfig { - /** - * Flag to indicate whether the web interface should be served by the OpenDC API server. - */ - @ConfigItem(defaultValue = "true") - public boolean enable; -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml deleted file mode 100644 index 581a1779..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -name: "OpenDC Web UI" -metadata: - status: "preview" - unlisted: true diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java new file mode 100644 index 00000000..026a9039 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * 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 org.opendc.web.ui.runtime; + +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.vertx.http.runtime.devmode.FileSystemStaticHandler; +import io.quarkus.vertx.http.runtime.webjar.WebJarNotFoundHandler; +import io.quarkus.vertx.http.runtime.webjar.WebJarStaticHandler; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Helper class for serving the OpenDC web interface. + */ +@Recorder +public class OpenDCUiRecorder { + /** + * Construct a {@link Handler} for serving a page of the OpenDC web interface. + */ + public Handler pageHandler( + String finalDestination, + String page, + OpenDCUiRuntimeConfig runtimeConfig + ) { + if (runtimeConfig.enable) { + String pageDirectory = finalDestination + "/pages"; + return (event) -> { + event.response() + .setStatusCode(200) + .sendFile(pageDirectory + page + ".html"); + }; + } + + return new WebJarNotFoundHandler(); + } + + /** + * Construct a {@link Handler} for handling redirects in the OpenDC web interface. + */ + public Handler redirectHandler( + String destination, + int statusCode, + OpenDCUiRuntimeConfig runtimeConfig + ) { + if (runtimeConfig.enable) { + return (event) -> { + String query = event.request().query(); + String fullDestination = query != null ? destination + "?" + query : destination; + + event.response() + .setStatusCode(statusCode) + .putHeader("Location", fullDestination) + .end(); + }; + } + + return new WebJarNotFoundHandler(); + } + + /** + * Construct a {@link Handler} for serving the static files of the OpenDC web interface. + */ + public Handler staticHandler( + String finalDestination, + String path, + List webRootConfigurations, + OpenDCUiRuntimeConfig runtimeConfig, + ShutdownContext shutdownContext + ) { + if (runtimeConfig.enable) { + var augmentedWebRootConfigurations = webRootConfigurations + .stream() + .map(c -> new FileSystemStaticHandler.StaticWebRootConfiguration(c.getFileSystem(), c.getWebRoot().isEmpty() ? "static" : c.getWebRoot() + "/static")) + .collect(Collectors.toList()); + + WebJarStaticHandler handler = new WebJarStaticHandler(finalDestination + "/static", path, augmentedWebRootConfigurations); + shutdownContext.addShutdownTask(new ShutdownContext.CloseRunnable(handler)); + return handler; + } + + return new WebJarNotFoundHandler(); + } +} diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java new file mode 100644 index 00000000..8ae3b6a2 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * 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 org.opendc.web.ui.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Configuration for the OpenDC web UI. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-ui") +public class OpenDCUiRuntimeConfig { + /** + * Flag to indicate whether the web interface should be served by the OpenDC API server. + */ + @ConfigItem(defaultValue = "true") + public boolean enable; +} diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000..581a1779 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,5 @@ +--- +name: "OpenDC Web UI" +metadata: + status: "preview" + unlisted: true diff --git a/settings.gradle.kts b/settings.gradle.kts index d3f3dc9d..f4ae69e4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,8 +39,8 @@ include(":opendc-web:opendc-web-proto") include(":opendc-web:opendc-web-api") include(":opendc-web:opendc-web-client") include(":opendc-web:opendc-web-ui") -include(":opendc-web:opendc-web-ui-quarkus:deployment") -include(":opendc-web:opendc-web-ui-quarkus:runtime") +include(":opendc-web:opendc-web-ui-quarkus") +include(":opendc-web:opendc-web-ui-quarkus-deployment") include(":opendc-web:opendc-web-runner:opendc-web-runner") include(":opendc-simulator:opendc-simulator-core") include(":opendc-simulator:opendc-simulator-flow") -- cgit v1.2.3