All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.devui.deployment.DevUIProcessor Maven / Gradle / Ivy

package io.quarkus.devui.deployment;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.stream.Collectors;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.yaml.snakeyaml.Yaml;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.deployment.IsDevelopment;
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.AdditionalIndexedClassesBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.dev.console.DevConsoleManager;
import io.quarkus.devui.deployment.extension.Codestart;
import io.quarkus.devui.deployment.extension.Extension;
import io.quarkus.devui.deployment.jsonrpc.DevUIDatabindCodec;
import io.quarkus.devui.runtime.DevUIRecorder;
import io.quarkus.devui.runtime.comms.JsonRpcRouter;
import io.quarkus.devui.runtime.jsonrpc.JsonRpcMethod;
import io.quarkus.devui.runtime.jsonrpc.JsonRpcMethodName;
import io.quarkus.devui.runtime.jsonrpc.json.JsonMapper;
import io.quarkus.devui.spi.DevUIContent;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.buildtime.StaticContentBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.FooterPageBuildItem;
import io.quarkus.devui.spi.page.MenuPageBuildItem;
import io.quarkus.devui.spi.page.Page;
import io.quarkus.devui.spi.page.PageBuilder;
import io.quarkus.devui.spi.page.QuteDataPageBuilder;
import io.quarkus.maven.dependency.GACT;
import io.quarkus.maven.dependency.GACTV;
import io.quarkus.qute.Qute;
import io.quarkus.runtime.util.ClassPathUtils;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
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.smallrye.common.annotation.Blocking;
import io.smallrye.common.annotation.NonBlocking;
import io.smallrye.mutiny.Multi;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

/**
 * Create the HTTP related Dev UI API Points.
 * This includes the JsonRPC Websocket endpoint and the endpoints that deliver the generated and static content.
 *
 * This also find all jsonrpc methods and make them available in the jsonRPC Router
 */
public class DevUIProcessor {

    private static final String DEVUI = "dev-ui";
    private static final String SLASH = "/";
    private static final String DOT = ".";
    private static final String DOUBLE_POINT = ":";
    private static final String DASH_DEPLOYMENT = "-deployment";
    private static final String SLASH_ALL = SLASH + "*";
    private static final String JSONRPC = "json-rpc-ws";

    private static final String CONSTRUCTOR = "";

    private final ClassLoader tccl = Thread.currentThread().getContextClassLoader();

    private static final String JAR = "jar";
    private static final GACT UI_JAR = new GACT("io.quarkus", "quarkus-vertx-http-dev-ui-resources", null, JAR);
    private static final String YAML_FILE = "/META-INF/quarkus-extension.yaml";
    private static final String NAME = "name";
    private static final String DESCRIPTION = "description";
    private static final String ARTIFACT = "artifact";
    private static final String METADATA = "metadata";
    private static final String KEYWORDS = "keywords";
    private static final String SHORT_NAME = "short-name";
    private static final String GUIDE = "guide";
    private static final String CATEGORIES = "categories";
    private static final String STATUS = "status";
    private static final String BUILT_WITH = "built-with-quarkus-core";
    private static final String CONFIG = "config";
    private static final String EXTENSION_DEPENDENCIES = "extension-dependencies";
    private static final String CAPABILITIES = "capabilities";
    private static final String PROVIDES = "provides";
    private static final String UNLISTED = "unlisted";
    private static final String CODESTART = "codestart";
    private static final String LANGUAGES = "languages";

    private static final Logger log = Logger.getLogger(DevUIProcessor.class);

    @BuildStep(onlyIf = IsDevelopment.class)
    @Record(ExecutionTime.STATIC_INIT)
    void registerDevUiHandlers(
            MvnpmBuildItem mvnpmBuildItem,
            List devUIRoutesBuildItems,
            List staticContentBuildItems,
            BuildProducer routeProducer,
            DevUIRecorder recorder,
            LaunchModeBuildItem launchModeBuildItem,
            NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
            ShutdownContextBuildItem shutdownContext) throws IOException {

        if (launchModeBuildItem.isNotLocalDevModeType()) {
            return;
        }

        // Websocket for JsonRPC comms
        routeProducer.produce(
                nonApplicationRootPathBuildItem
                        .routeBuilder().route(DEVUI + SLASH + JSONRPC)
                        .handler(recorder.communicationHandler())
                        .build());

        // Static handler for components
        for (DevUIRoutesBuildItem devUIRoutesBuildItem : devUIRoutesBuildItems) {
            String route = devUIRoutesBuildItem.getPath();

            String path = nonApplicationRootPathBuildItem.resolvePath(route);
            Handler uihandler = recorder.uiHandler(
                    devUIRoutesBuildItem.getFinalDestination(),
                    path,
                    devUIRoutesBuildItem.getWebRootConfigurations(),
                    shutdownContext);

            NonApplicationRootPathBuildItem.Builder builder = nonApplicationRootPathBuildItem.routeBuilder()
                    .route(route)
                    .handler(uihandler);

            if (route.endsWith(DEVUI)) {
                builder = builder.displayOnNotFoundPage("Dev UI 2.0");
                routeProducer.produce(builder.build());
            }

            routeProducer.produce(
                    nonApplicationRootPathBuildItem.routeBuilder().route(route + SLASH_ALL).handler(uihandler).build());
        }

        String basepath = nonApplicationRootPathBuildItem.resolvePath(DEVUI);
        // For static content generated at build time
        Path devUiBasePath = Files.createTempDirectory("quarkus-devui");
        recorder.shutdownTask(shutdownContext, devUiBasePath.toString());

        for (StaticContentBuildItem staticContentBuildItem : staticContentBuildItems) {

            Map urlAndPath = new HashMap<>();

            List content = staticContentBuildItem.getContent();
            for (DevUIContent c : content) {
                String parsedContent = Qute.fmt(new String(c.getTemplate()), c.getData());
                Path tempFile = devUiBasePath
                        .resolve(c.getFileName());
                Files.write(tempFile, parsedContent.getBytes(StandardCharsets.UTF_8));

                urlAndPath.put(c.getFileName(), tempFile.toString());
            }
            Handler buildTimeStaticHandler = recorder.buildTimeStaticHandler(basepath, urlAndPath);

            routeProducer.produce(
                    nonApplicationRootPathBuildItem.routeBuilder().route(DEVUI + SLASH_ALL)
                            .handler(buildTimeStaticHandler)
                            .build());
        }

        // For the Vaadin router (So that bookmarks/url refreshes work)
        for (DevUIRoutesBuildItem devUIRoutesBuildItem : devUIRoutesBuildItems) {
            String route = devUIRoutesBuildItem.getPath();
            basepath = nonApplicationRootPathBuildItem.resolvePath(route);
            Handler routerhandler = recorder.vaadinRouterHandler(basepath);
            routeProducer.produce(
                    nonApplicationRootPathBuildItem.routeBuilder().route(route + SLASH_ALL).handler(routerhandler).build());
        }

        // Static mvnpm jars
        String contextRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath();
        routeProducer.produce(
                nonApplicationRootPathBuildItem.routeBuilder()
                        .route("_static" + SLASH_ALL)
                        .handler(recorder.mvnpmHandler(contextRoot, mvnpmBuildItem.getMvnpmJars()))
                        .build());

        // Redirect /q/dev -> /q/dev-ui
        routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
                .route("dev")
                .handler(recorder.redirect(contextRoot))
                .build());
    }

    /**
     * This makes sure the JsonRPC Classes for both the internal Dev UI and extensions is available as a bean and on the index.
     */
    @BuildStep(onlyIf = IsDevelopment.class)
    void additionalBean(BuildProducer additionalBeanProducer,
            BuildProducer additionalIndexProducer,
            List jsonRPCProvidersBuildItems) {

        additionalBeanProducer.produce(AdditionalBeanBuildItem.builder()
                .addBeanClass(JsonRpcRouter.class)
                .setUnremovable().build());

        // Make sure all JsonRPC Providers is in the index
        for (JsonRPCProvidersBuildItem jsonRPCProvidersBuildItem : jsonRPCProvidersBuildItems) {

            Class c = jsonRPCProvidersBuildItem.getJsonRPCMethodProviderClass();
            additionalIndexProducer.produce(new AdditionalIndexedClassesBuildItem(c.getName()));

            additionalBeanProducer.produce(AdditionalBeanBuildItem.builder()
                    .addBeanClass(c)
                    .setDefaultScope(BuiltinScope.APPLICATION.getName())
                    .setUnremovable().build());
        }

        additionalBeanProducer.produce(AdditionalBeanBuildItem.builder()
                .addBeanClass(JsonRpcRouter.class)
                .setDefaultScope(BuiltinScope.APPLICATION.getName())
                .setUnremovable().build());

    }

    /**
     * This goes through all jsonRPC methods and discover the methods using Jandex
     */
    @BuildStep(onlyIf = IsDevelopment.class)
    void findAllJsonRPCMethods(BuildProducer jsonRPCMethodsProvider,
            BuildProducer buildTimeConstProducer,
            LaunchModeBuildItem launchModeBuildItem,
            CombinedIndexBuildItem combinedIndexBuildItem,
            CurateOutcomeBuildItem curateOutcomeBuildItem,
            List jsonRPCProvidersBuildItems) {

        if (launchModeBuildItem.isNotLocalDevModeType()) {
            return;
        }

        IndexView index = combinedIndexBuildItem.getIndex();

        Map> extensionMethodsMap = new HashMap<>(); // All methods so that we can build the reflection

        List requestResponseMethods = new ArrayList<>(); // All requestResponse methods for validation on the client side
        List subscriptionMethods = new ArrayList<>(); // All subscription methods for validation on the client side

        // Let's use the Jandex index to find all methods
        for (JsonRPCProvidersBuildItem jsonRPCProvidersBuildItem : jsonRPCProvidersBuildItems) {

            Class clazz = jsonRPCProvidersBuildItem.getJsonRPCMethodProviderClass();
            String extension = jsonRPCProvidersBuildItem.getExtensionPathName(curateOutcomeBuildItem);
            Map jsonRpcMethods = new HashMap<>();
            if (extensionMethodsMap.containsKey(extension)) {
                jsonRpcMethods = extensionMethodsMap.get(extension);
            }

            ClassInfo classInfo = index.getClassByName(DotName.createSimple(clazz.getName()));

            List methods = classInfo.methods();

            for (MethodInfo method : methods) {
                if (!method.name().equals(CONSTRUCTOR)) { // Ignore constructor
                    if (Modifier.isPublic(method.flags())) { // Only allow public methods
                        if (method.returnType().kind() != Type.Kind.VOID) { // Only allow method with response

                            // Create list of available methods for the Javascript side.
                            if (method.returnType().name().equals(DotName.createSimple(Multi.class.getName()))) {
                                subscriptionMethods.add(extension + DOT + method.name());
                            } else {
                                requestResponseMethods.add(extension + DOT + method.name());
                            }

                            // Also create the map to pass to the runtime for the relection calls
                            JsonRpcMethodName jsonRpcMethodName = new JsonRpcMethodName(method.name());
                            if (method.parametersCount() > 0) {
                                Map params = new LinkedHashMap<>(); // Keep the order
                                for (int i = 0; i < method.parametersCount(); i++) {
                                    Type parameterType = method.parameterType(i);
                                    Class parameterClass = toClass(parameterType);
                                    String parameterName = method.parameterName(i);
                                    params.put(parameterName, parameterClass);
                                }
                                JsonRpcMethod jsonRpcMethod = new JsonRpcMethod(clazz, method.name(), params);
                                jsonRpcMethod.setExplicitlyBlocking(method.hasAnnotation(Blocking.class));
                                jsonRpcMethod
                                        .setExplicitlyNonBlocking(method.hasAnnotation(NonBlocking.class));
                                jsonRpcMethods.put(jsonRpcMethodName, jsonRpcMethod);
                            } else {
                                JsonRpcMethod jsonRpcMethod = new JsonRpcMethod(clazz, method.name(), null);
                                jsonRpcMethod.setExplicitlyBlocking(method.hasAnnotation(Blocking.class));
                                jsonRpcMethod
                                        .setExplicitlyNonBlocking(method.hasAnnotation(NonBlocking.class));
                                jsonRpcMethods.put(jsonRpcMethodName, jsonRpcMethod);
                            }
                        }
                    }
                }
            }

            if (!jsonRpcMethods.isEmpty()) {
                extensionMethodsMap.put(extension, jsonRpcMethods);
            }
        }

        if (!extensionMethodsMap.isEmpty()) {
            jsonRPCMethodsProvider.produce(new JsonRPCMethodsBuildItem(extensionMethodsMap));
        }

        BuildTimeConstBuildItem methodInfo = new BuildTimeConstBuildItem("devui-jsonrpc");

        if (!subscriptionMethods.isEmpty()) {
            methodInfo.addBuildTimeData("jsonRPCSubscriptions", subscriptionMethods);
        }
        if (!requestResponseMethods.isEmpty()) {
            methodInfo.addBuildTimeData("jsonRPCMethods", requestResponseMethods);
        }

        buildTimeConstProducer.produce(methodInfo);

    }

    @BuildStep(onlyIf = IsDevelopment.class)
    @Record(ExecutionTime.STATIC_INIT)
    void createJsonRpcRouter(DevUIRecorder recorder,
            BeanContainerBuildItem beanContainer,
            JsonRPCMethodsBuildItem jsonRPCMethodsBuildItem) {

        if (jsonRPCMethodsBuildItem != null) {
            Map> extensionMethodsMap = jsonRPCMethodsBuildItem
                    .getExtensionMethodsMap();

            DevConsoleManager.setGlobal(DevUIRecorder.DEV_MANAGER_GLOBALS_JSON_MAPPER_FACTORY,
                    JsonMapper.Factory.deploymentLinker().createLinkData(new DevUIDatabindCodec.Factory()));
            recorder.createJsonRpcRouter(beanContainer.getValue(), extensionMethodsMap);
        }
    }

    /**
     * This build all the pages for dev ui, based on the extension included
     */
    @BuildStep(onlyIf = IsDevelopment.class)
    @SuppressWarnings("unchecked")
    void getAllExtensions(List cardPageBuildItems,
            List menuPageBuildItems,
            List footerPageBuildItems,
            LaunchModeBuildItem launchModeBuildItem,
            CurateOutcomeBuildItem curateOutcomeBuildItem,
            BuildProducer extensionsProducer,
            BuildProducer webJarBuildProducer,
            BuildProducer devUIWebJarProducer) {

        if (launchModeBuildItem.isNotLocalDevModeType()) {
            // produce extension build item as cascade of build steps rely on it
            var emptyExtensionBuildItem = new ExtensionsBuildItem(List.of(), List.of(), List.of(), List.of());
            extensionsProducer.produce(emptyExtensionBuildItem);
            return;
        }

        // First create the static resources for our own internal components
        webJarBuildProducer.produce(WebJarBuildItem.builder()
                .artifactKey(UI_JAR)
                .root(DEVUI + SLASH).build());

        devUIWebJarProducer.produce(new DevUIWebJarBuildItem(UI_JAR, DEVUI));

        // Now go through all extensions and check them for active components
        Map cardPagesMap = getCardPagesMap(curateOutcomeBuildItem, cardPageBuildItems);
        Map menuPagesMap = getMenuPagesMap(curateOutcomeBuildItem, menuPageBuildItems);
        Map footerPagesMap = getFooterPagesMap(curateOutcomeBuildItem, footerPageBuildItems);
        try {
            final Yaml yaml = new Yaml();
            List activeExtensions = new ArrayList<>();
            List inactiveExtensions = new ArrayList<>();
            List sectionMenuExtensions = new ArrayList<>();
            List footerTabExtensions = new ArrayList<>();
            ClassPathUtils.consumeAsPaths(YAML_FILE, (Path p) -> {
                try {
                    Extension extension = new Extension();
                    final String extensionYaml;
                    try (Scanner scanner = new Scanner(Files.newBufferedReader(p, StandardCharsets.UTF_8))) {
                        scanner.useDelimiter("\\A");
                        extensionYaml = scanner.hasNext() ? scanner.next() : null;
                    }
                    if (extensionYaml == null) {
                        // This is a internal extension (like this one, Dev UI)
                        return;
                    }

                    final Map extensionMap = yaml.load(extensionYaml);

                    if (extensionMap.containsKey(NAME)) {
                        String namespace = getExtensionNamespace(extensionMap);
                        extension.setNamespace(namespace);
                        extension.setName((String) extensionMap.get(NAME));
                        extension.setDescription((String) extensionMap.getOrDefault(DESCRIPTION, null));
                        String artifactId = (String) extensionMap.getOrDefault(ARTIFACT, null);
                        extension.setArtifact(artifactId);

                        Map metaData = (Map) extensionMap.getOrDefault(METADATA, null);
                        extension.setKeywords((List) metaData.getOrDefault(KEYWORDS, null));
                        extension.setShortName((String) metaData.getOrDefault(SHORT_NAME, null));

                        if (metaData.containsKey(GUIDE)) {
                            String guide = (String) metaData.get(GUIDE);
                            try {
                                extension.setGuide(new URL(guide));
                            } catch (MalformedURLException mue) {
                                log.warn("Could not set Guide URL [" + guide + "] for exception [" + namespace + "]");
                            }
                        }

                        extension.setCategories((List) metaData.getOrDefault(CATEGORIES, null));
                        extension.setStatus(collectionToString(metaData, STATUS));
                        extension.setBuiltWith((String) metaData.getOrDefault(BUILT_WITH, null));
                        extension.setConfigFilter((List) metaData.getOrDefault(CONFIG, null));
                        extension.setExtensionDependencies((List) metaData.getOrDefault(EXTENSION_DEPENDENCIES, null));
                        extension.setUnlisted(String.valueOf(metaData.getOrDefault(UNLISTED, false)));

                        if (metaData.containsKey(CAPABILITIES)) {
                            Map capabilities = (Map) metaData.get(CAPABILITIES);
                            extension.setProvidesCapabilities((List) capabilities.getOrDefault(PROVIDES, null));
                        }

                        if (metaData.containsKey(CODESTART)) {
                            Map codestartMap = (Map) metaData.get(metaData);
                            if (codestartMap != null) {
                                Codestart codestart = new Codestart();
                                codestart.setName((String) codestartMap.getOrDefault(NAME, null));
                                codestart.setLanguages((List) codestartMap.getOrDefault(LANGUAGES, null));
                                codestart.setArtifact((String) codestartMap.getOrDefault(ARTIFACT, null));
                                extension.setCodestart(codestart);
                            }
                        }

                        if (!cardPagesMap.containsKey(namespace)) { // Inactive
                            inactiveExtensions.add(extension);
                        } else { // Active
                            CardPageBuildItem cardPageBuildItem = cardPagesMap.get(namespace);

                            // Add all card links
                            List cardPageBuilders = cardPageBuildItem.getPages();

                            Map buildTimeData = cardPageBuildItem.getBuildTimeData();
                            for (PageBuilder pageBuilder : cardPageBuilders) {
                                Page page = buildFinalPage(pageBuilder, extension, buildTimeData);
                                extension.addCardPage(page);
                            }

                            // See if there is a custom card component
                            cardPageBuildItem.getOptionalCard().ifPresent((card) -> {
                                card.setNamespace(extension.getNamespace());
                                extension.setCard(card);
                            });

                            // Also make sure the static resources for that static resource is available
                            produceResources(artifactId, webJarBuildProducer,
                                    devUIWebJarProducer);
                            activeExtensions.add(extension);
                        }

                        // Menus on the sections menu
                        if (menuPagesMap.containsKey(namespace)) {
                            MenuPageBuildItem menuPageBuildItem = menuPagesMap.get(namespace);
                            List menuPageBuilders = menuPageBuildItem.getPages();

                            Map buildTimeData = menuPageBuildItem.getBuildTimeData();
                            for (PageBuilder pageBuilder : menuPageBuilders) {
                                Page page = buildFinalPage(pageBuilder, extension, buildTimeData);
                                extension.addMenuPage(page);
                            }
                            // Also make sure the static resources for that static resource is available
                            produceResources(artifactId, webJarBuildProducer,
                                    devUIWebJarProducer);
                            sectionMenuExtensions.add(extension);
                        }

                        // Tabs in the footer
                        if (footerPagesMap.containsKey(namespace)) {
                            FooterPageBuildItem footerPageBuildItem = footerPagesMap.get(namespace);
                            List footerPageBuilders = footerPageBuildItem.getPages();

                            Map buildTimeData = footerPageBuildItem.getBuildTimeData();
                            for (PageBuilder pageBuilder : footerPageBuilders) {
                                Page page = buildFinalPage(pageBuilder, extension, buildTimeData);
                                extension.addFooterPage(page);
                            }
                            // Also make sure the static resources for that static resource is available
                            produceResources(artifactId, webJarBuildProducer,
                                    devUIWebJarProducer);
                            footerTabExtensions.add(extension);
                        }

                    }

                    Collections.sort(activeExtensions, sortingComparator);
                    Collections.sort(inactiveExtensions, sortingComparator);
                } catch (IOException | RuntimeException e) {
                    // don't abort, just log, to prevent a single extension from breaking entire dev ui
                    log.error("Failed to process extension descriptor " + p.toUri(), e);
                }
            });
            extensionsProducer.produce(
                    new ExtensionsBuildItem(activeExtensions, inactiveExtensions, sectionMenuExtensions, footerTabExtensions));
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private String collectionToString(Map metaData, String key) {
        Object value = metaData.getOrDefault(key, null);
        if (value == null) {
            return null;
        } else if (String.class.isAssignableFrom(value.getClass())) {
            return (String) value;
        } else if (List.class.isAssignableFrom(value.getClass())) {
            List values = (List) value;
            return (String) values.stream()
                    .map(n -> String.valueOf(n))
                    .collect(Collectors.joining(", "));
        }
        return String.valueOf(value);
    }

    private void produceResources(String artifactId,
            BuildProducer webJarBuildProducer,
            BuildProducer devUIWebJarProducer) {

        GACT gact = getGACT(artifactId);
        String namespace = getNamespace(gact);
        if (namespace.isEmpty()) {
            namespace = "devui";
        }
        String buildTimeDataImport = namespace + "-data";

        webJarBuildProducer.produce(WebJarBuildItem.builder()
                .artifactKey(gact)
                .root(DEVUI + SLASH)
                .filter(new WebJarResourcesFilter() {
                    @Override
                    public WebJarResourcesFilter.FilterResult apply(String fileName, InputStream file) throws IOException {
                        if (fileName.endsWith(".js")) {
                            String content = new String(file.readAllBytes(), StandardCharsets.UTF_8);
                            content = content.replaceAll("build-time-data", buildTimeDataImport);
                            return new WebJarResourcesFilter.FilterResult(
                                    new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), true);
                        }

                        return new WebJarResourcesFilter.FilterResult(file, false);
                    }
                })
                .build());

        devUIWebJarProducer.produce(
                new DevUIWebJarBuildItem(gact,
                        DEVUI));
    }

    @BuildStep(onlyIf = IsDevelopment.class)
    void createAllRoutes(WebJarResultsBuildItem webJarResultsBuildItem,
            LaunchModeBuildItem launchModeBuildItem,
            List devUIWebJarBuiltItems,
            BuildProducer devUIRoutesProducer) {

        if (launchModeBuildItem.isNotLocalDevModeType()) {
            return;
        }

        for (DevUIWebJarBuildItem devUIWebJarBuiltItem : devUIWebJarBuiltItems) {
            WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem
                    .byArtifactKey(devUIWebJarBuiltItem.getArtifactKey());
            if (result != null) {
                String namespace = getNamespace(devUIWebJarBuiltItem.getArtifactKey());
                devUIRoutesProducer.produce(new DevUIRoutesBuildItem(namespace, devUIWebJarBuiltItem.getPath(),
                        result.getFinalDestination(), result.getWebRootConfigurations()));
            }
        }
    }

    private String getNamespace(GACT artifactKey) {
        String namespace = artifactKey.getGroupId() + "." + artifactKey.getArtifactId();

        if (namespace.equals("io.quarkus.quarkus-vertx-http-dev-ui-resources")) {
            // Internal
            namespace = "";
        } else if (namespace.endsWith("-deployment")) {
            int end = namespace.lastIndexOf("-");
            namespace = namespace.substring(0, end);
        }
        return namespace;
    }

    private Page buildFinalPage(PageBuilder pageBuilder, Extension extension, Map buildTimeData) {
        pageBuilder.namespace(extension.getNamespace());
        pageBuilder.extension(extension.getName());

        // TODO: Have a nice factory way to load this...
        // Some preprocessing for certain builds
        if (pageBuilder.getClass().equals(QuteDataPageBuilder.class)) {
            return buildQutePage(pageBuilder, extension, buildTimeData);
        }

        return pageBuilder.build();
    }

    private Page buildQutePage(PageBuilder pageBuilder, Extension extension, Map buildTimeData) {
        try {
            QuteDataPageBuilder quteDataPageBuilder = (QuteDataPageBuilder) pageBuilder;
            String templatePath = quteDataPageBuilder.getTemplatePath();
            ClassPathUtils.consumeAsPaths(templatePath, p -> {
                try {
                    String template = Files.readString(p);
                    String fragment = Qute.fmt(template, buildTimeData);
                    pageBuilder.metadata("htmlFragment", fragment);
                } catch (IOException ex) {
                    throw new UncheckedIOException(ex);
                }
            });
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
        return pageBuilder.build();
    }

    private GACT getGACT(String artifactKey) {
        String[] split = artifactKey.split(DOUBLE_POINT);
        return new GACT(split[0], split[1] + DASH_DEPLOYMENT, null, JAR);
    }

    private Class toClass(Type type) {
        try {
            return tccl.loadClass(type.name().toString());
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
    }

    private Map getCardPagesMap(CurateOutcomeBuildItem curateOutcomeBuildItem,
            List pages) {
        Map m = new HashMap<>();
        for (CardPageBuildItem pageBuildItem : pages) {
            String name = pageBuildItem.getExtensionPathName(curateOutcomeBuildItem);
            m.put(name, pageBuildItem);
        }
        return m;
    }

    private Map getMenuPagesMap(CurateOutcomeBuildItem curateOutcomeBuildItem,
            List pages) {
        Map m = new HashMap<>();
        for (MenuPageBuildItem pageBuildItem : pages) {
            m.put(pageBuildItem.getExtensionPathName(curateOutcomeBuildItem), pageBuildItem);
        }
        return m;
    }

    private Map getFooterPagesMap(CurateOutcomeBuildItem curateOutcomeBuildItem,
            List pages) {
        Map m = new HashMap<>();
        for (FooterPageBuildItem pageBuildItem : pages) {
            m.put(pageBuildItem.getExtensionPathName(curateOutcomeBuildItem), pageBuildItem);
        }
        return m;
    }

    private String getExtensionNamespace(Map extensionMap) {
        final String groupId;
        final String artifactId;
        final String artifact = (String) extensionMap.get("artifact");
        if (artifact == null) {
            // trying quarkus 1.x format
            groupId = (String) extensionMap.get("group-id");
            artifactId = (String) extensionMap.get("artifact-id");
            if (artifactId == null || groupId == null) {
                throw new RuntimeException(
                        "Failed to locate 'artifact' or 'group-id' and 'artifact-id' among metadata keys "
                                + extensionMap.keySet());
            }
        } else {
            final GACTV coords = GACTV.fromString(artifact);
            groupId = coords.getGroupId();
            artifactId = coords.getArtifactId();
        }
        return groupId + "." + artifactId;
    }

    // Sort extensions with Guide first and then alphabetical
    private final Comparator sortingComparator = new Comparator() {
        @Override
        public int compare(Extension t, Extension t1) {
            if (t.getGuide() != null && t1.getGuide() != null) {
                return t.getName().compareTo(t1.getName());
            } else if (t.getGuide() == null) {
                return 1;
            } else {
                return -1;
            }
        }
    };
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy