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

org.wildfly.plugins.bootablejar.maven.goals.DevWatchBootableJarMojo Maven / Gradle / Ivy

There is a newer version: 11.0.2.Final
Show newest version
/*
 * Copyright 2020 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.wildfly.plugins.bootablejar.maven.goals;

import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.eclipse.aether.repository.RemoteRepository;

import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.galleon.ProvisioningException;
import org.jboss.galleon.util.IoUtils;
import org.jboss.galleon.util.ZipUtils;
import org.twdata.maven.mojoexecutor.MojoExecutor;
import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment;
import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
import static org.twdata.maven.mojoexecutor.MojoExecutor.version;
import org.wildfly.core.launcher.Launcher;
import org.wildfly.plugins.bootablejar.maven.common.Utils;
import org.wildfly.plugins.bootablejar.maven.goals.DevWatchContext.BootableAppEventHandler;
import org.wildfly.plugins.bootablejar.maven.goals.DevWatchContext.ProjectContext;
import org.jboss.as.controller.client.helpers.Operations;
import org.jboss.dmr.ModelNode;
import org.wildfly.core.launcher.BootableJarCommandBuilder;
import org.wildfly.plugin.common.PropertyNames;
import org.wildfly.plugin.core.ServerHelper;

/**
 * Build and start a bootable JAR for dev-watch mode. This goal monitors the
 * changes in the project and recompile/re-deploy. Type Ctrl-C to kill the
 * running server.
 *
 * @author jfdenise
 */
@Mojo(name = "dev-watch", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.COMPILE)
public final class DevWatchBootableJarMojo extends AbstractDevBootableJarMojo {

    private static final String MANAGEMENT_LAYER = "management";

    /**
     * running any one of these phases means the compile phase will have been
     * run, if these have not been run we manually run compile
     */
    private static final Set POST_COMPILE_PHASES = new HashSet<>(Arrays.asList(
            "compile",
            "process-classes",
            "generate-test-sources",
            "process-test-sources",
            "generate-test-resources",
            "process-test-resources",
            "test-compile",
            "process-test-classes",
            "test",
            "prepare-package",
            "package",
            "pre-integration-test",
            "integration-test",
            "post-integration-test",
            "verify",
            "install",
            "deploy"));
    private static final String ORG_APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins";
    private static final String MAVEN_COMPILER_PLUGIN = "maven-compiler-plugin";
    private static final String MAVEN_COMPILER_GOAL = "compile";
    private static final String MAVEN_WAR_PLUGIN = "maven-war-plugin";
    private static final String MAVEN_EXPLODED_GOAL = "exploded";
    private static final String MAVEN_JAR_PLUGIN = "maven-jar-plugin";
    private static final String MAVEN_EJB_PLUGIN = "maven-ejb-plugin";
    private static final String MAVEN_JAR_GOAL = "jar";
    private static final String MAVEN_EJB_GOAL = "ejb";
    private static final String MAVEN_RESOURCES_PLUGIN = "maven-resources-plugin";
    private static final String MAVEN_RESOURCES_GOAL = "resources";
    private static final String REBUILD_MARKER = "wildfly.bootable.jar.rebuild";

    private static final String MAVEN_WILDFLY_JAR_PLUGINS = "org.wildfly.plugins";
    private static final String MAVEN_WILDFLY_JAR_PLUGIN = "wildfly-jar-maven-plugin";
    private static final String WATCH_GOAL = "dev-watch";
    private static final boolean IS_WINDOWS;
    private static final String DEBUG_AGENT_OPTION="-agentlib:jdwp=";
    static {
        final String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
        IS_WINDOWS = os.contains("win");
    }

    @Component
    private BuildPluginManager pluginManager;

    @Component
    private ProjectBuilder projectBuilder;

    @Parameter(defaultValue = "${project.build.sourceDirectory}")
    private File sourceDir;

    /**
     * Specifies the host name of the server where the deployment plan should be
     * executed.
     */
    @Parameter(defaultValue = "localhost", property = PropertyNames.HOSTNAME)
    private String hostname;

    /**
     * Specifies the port number the server is listening on.
     */
    @Parameter(defaultValue = "9990", property = PropertyNames.PORT)
    private int port;

    /**
     * The timeout, in seconds, to wait for a management connection.
     */
    @Parameter(property = PropertyNames.TIMEOUT, defaultValue = "60")
    protected int timeout;

    @Parameter(defaultValue = "${project.remotePluginRepositories}", readonly = true, required = true)
    private List pluginRepos;

    /**
     * Enable/Disable debug. If debugger is explicitly enabled in JVM arguments, this option has no effect,
     */
    @Parameter(defaultValue = "false", property = "wildfly.bootable.debug")
    private boolean debug;

    /**
     * Debug port.
     */
    @Parameter(alias= "debug-port", defaultValue = "8787", property = "wildfly.bootable.debug.port")
    private int debugPort;

    /**
     * Debug suspend.
     */
    @Parameter(alias= "debug-suspend", defaultValue = "false", property = "wildfly.bootable.debug.suspend")
    private boolean debugSuspend;

    private Process process;
    private Path currentServerDir;
    private final DeploymentController deploymentController = new DeploymentController();

    // Test specific content to have the process to exit on Windows
    static final String TEST_PROPERTY_EXIT = "dev-watch.test.exit.on.file";

    @Override
    protected void configureServer() {
        getLog().info("Dev mode, adding layer " + MANAGEMENT_LAYER + " to ensure dev mode can be operated");
        addExtraLayer(MANAGEMENT_LAYER);
    }

    private class DeploymentController {

        void deploy(Path dir) throws Exception {
            if (process == null) {
                return;
            }
            String name = dir.getFileName().toString();
            try (ModelControllerClient client = createClient()) {
                ServerHelper.waitForStandalone(client, timeout);
                undeploy(client, name);
                waitRemoved(client, name);
                boolean success = deploy(client, dir);
                if (success) {
                    waitDeploymentUp(client, name);
                }
                // We only need this on Windows since it may lock the JAR when the delete process is running
                if (IS_WINDOWS) {
                    currentServerDir = getHomeDirectory(client);
                }
            }
        }

        void waitRemoved(ModelControllerClient client, String name) throws Exception {
            ModelNode address = new ModelNode();
            address.add("deployment", name);
            waitStatus(client, "failed", Operations.createOperation("read-resource", address));
            getLog().debug("Deployment " + name + " removed");
        }

        void waitDeploymentUp(ModelControllerClient client, String name) throws Exception {
            ModelNode address = new ModelNode();
            address.add("deployment", name);
            ModelNode op = Operations.createOperation("read-attribute", address);
            op.get("name").set("status");
            ModelNode reply = waitStatus(client, "success", op);
            ModelNode result = reply.get("result");
            if (result.isDefined()) {
                String status = result.asString();
                if ("OK".equals(status)) {
                    getLog().debug("Deployment " + name + " is up");
                } else {
                    getLog().warn("Deployment " + name + " failed, status is " + status);
                }
            } else {
                throw new MojoExecutionException("No status returned for deployment " + name);
            }
        }

        void undeploy(ModelControllerClient client, String name) throws Exception {
            ModelNode composite = Operations.createCompositeOperation();
            ModelNode steps = composite.get("steps");
            ModelNode address = new ModelNode();
            address.add("deployment", name);
            steps.add(Operations.createOperation("undeploy", address));
            steps.add(Operations.createOperation("remove", address));
            getLog().debug("Undeploy " + name);
            client.execute(composite);
            getLog().debug("Undeploy " + name + " done");
        }

        boolean deploy(ModelControllerClient client, Path dir) throws Exception {
            ModelNode composite = Operations.createCompositeOperation();
            ModelNode steps = composite.get("steps");
            ModelNode address = new ModelNode();
            String name = dir.getFileName().toString();
            address.add("deployment", name);
            final ModelNode op = Operations.createOperation("add", address);
            ModelNode content = op.get("content").get(0);
            content.get("path").set(dir.toAbsolutePath().toString());
            content.get("archive").set(false);
            getLog().debug("Deploy " + name);
            steps.add(op);
            steps.add(Operations.createOperation("deploy", address));
            ModelNode reply = client.execute(composite);
            getLog().debug("Deploy " + name + " done");
            return "success".equals(reply.get("outcome").asString());
        }

        ModelNode waitStatus(ModelControllerClient client, String status, ModelNode op) throws Exception {
            int t = timeout * 1000;
            int waitTime = 100;
            while (t >= 0) {
                ModelNode reply = client.execute(op);
                if (status.equals(reply.get("outcome").asString())) {
                    return reply;
                }
                Thread.sleep(waitTime);
                t -= waitTime;
            }
            throw new MojoExecutionException("Timeout waiting for " + op + " to return " + status + " status");
        }
    }

    private class ProjectContextImpl implements DevWatchContext.ProjectContext {

        private final MavenProject currentProject;
        private final Xpp3Dom currentBootableJarConfig;
        private final Path projectBuildDir;
        private final boolean contextRoot;
        private final List cliSessions;
        private final List extraServerContent;
        private final Path sourceDir;

        ProjectContextImpl(MavenProject currentProject,
                Xpp3Dom currentBootableJarConfig,
                Path projectBuildDir,
                Path sourceDir,
                boolean contextRoot,
                List cliSessions,
                List extraServerContent) throws IOException {
            this.currentProject = currentProject;
            this.currentBootableJarConfig = currentBootableJarConfig;
            this.projectBuildDir = projectBuildDir;
            this.sourceDir = sourceDir;
            this.contextRoot = contextRoot;
            this.cliSessions = cliSessions;
            this.extraServerContent = extraServerContent;

        }

        @Override
        public final Path getBaseDir() {
            return currentProject.getBasedir().toPath();
        }

        @Override
        public final Path getSourceDir() {
            return getBaseDir().resolve("src");
        }

        @Override
        public final Path getJavaDir() {
            return sourceDir;
        }

        @Override
        public final Path getProjectBuildDir() {
            return projectBuildDir;
        }

        @Override
        public final Path getDeploymentsDir() {
            return DevWatchBootableJarMojo.this.getDeploymentsDir();
        }

        @Override
        public final Set getResources() {
            Set paths = new HashSet<>();
            for (Resource res : currentProject.getResources()) {
                paths.add(Paths.get(res.getDirectory()));
            }
            return paths;
        }

        @Override
        public final boolean isContextRoot() {
            return contextRoot;
        }

        @Override
        public final String getFinalName() {
            return currentProject.getBuild().getFinalName();
        }

        @Override
        public final Path getPomFile() {
            return currentProject.getBasedir().toPath().resolve("pom.xml");
        }

        @Override
        public final boolean isPluginConfigUpdated() throws ProjectBuildingException {
            MavenProject newProject = newProject(getPomFile());
            Plugin newPlugin = getPlugin(newProject);
            return !currentBootableJarConfig.equals(newPlugin.getConfiguration());
        }

        @Override
        public final List getCliSessions() {
            return cliSessions;
        }

        @Override
        public final List getExtraServerContent() {
            return extraServerContent;
        }

        @Override
        public final void debug(String msg) {
            getLog().debug(msg);
        }

        @Override
        public final void info(String msg) {
            getLog().info(msg);
        }

        @Override
        public final String getPackaging() {
            String packaging = currentProject.getPackaging();
            if ("ejb".equals(packaging)) {
                packaging = JAR;
            }
            return packaging;
        }

        @Override
        public final void cleanup() throws MojoExecutionException {
            getLog().debug("[WATCH] clean-up");
            IoUtils.recursiveDelete(getDeploymentsDir());
            cleanClasses(currentProject);
            triggerResources(currentProject);
        }

        @Override
        public final void compile(boolean autoCompile) throws MojoExecutionException {
            if (autoCompile) {
                handleAutoCompile(currentProject);
            } else {
                getLog().debug("[WATCH] compile");
                triggerCompile(currentProject);
            }
        }

        @Override
        public final void packageJar(Path targetDir, Path artifactFile) throws IOException, MojoExecutionException {
            triggerJar(currentProject);
            ZipUtils.unzip(artifactFile, targetDir);
        }

        @Override
        public final void packageWar(Path targetDir) throws MojoExecutionException {
            triggerExplodeWar(currentProject, targetDir);
        }

        @Override
        public void deploy(Path dir) throws Exception {
            deploymentController.deploy(dir);
        }

    }

    @Override
    protected void doExecute() throws MojoExecutionException, MojoFailureException {
        boolean isRebuild = System.getProperty(REBUILD_MARKER) != null;
        if (isRebuild) {
            return;
        }
        try {
            WatchService watcher = FileSystems.getDefault().newWatchService();
            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        watcher.close();
                    } catch (IOException ex) {
                        getLog().error("Error closing the watcher " + ex);
                    }
                    shutdownContainer();
                }
            }));

            ProjectContext projectContext = new ProjectContextImpl(project,
                    (Xpp3Dom) getPlugin(project).getConfiguration(),
                    Paths.get(projectBuildDir), sourceDir.toPath(), contextRoot, cliSessions, extraServerContentDirs);
            DevWatchContext ctx = new DevWatchContext(projectContext, watcher);
            ctx.build(true);
            process = Launcher.of(buildCommandBuilder(false))
                    .inherit()
                    .launch();
            deploymentController.deploy(ctx.getTargetDirectory());
            watch(watcher, ctx);
        } catch (Exception e) {
            throw new MojoExecutionException(e.getLocalizedMessage(), e);
        }
    }

    @Override
    protected BootableJarCommandBuilder buildCommandBuilder(boolean redirect) throws MojoExecutionException {
        BootableJarCommandBuilder builder = super.buildCommandBuilder(redirect);
        if (debug) {
            boolean custom=false;
            for (String opt : builder.getJavaOptions()) {
                if (opt.startsWith(DEBUG_AGENT_OPTION)) {
                    custom = true;
                    break;
                }
            }
            if (!custom) {
                builder.addJavaOption(DEBUG_AGENT_OPTION + "transport=dt_socket,address=" + (Utils.isModularJVM() ? "*:" : "") + debugPort +
                        ",server=y,suspend="+ (debugSuspend ? "y" : "n"));
            }
        }
        return builder;
    }

    private void watch(WatchService watcher, DevWatchContext ctx) throws IOException, MojoExecutionException, InterruptedException, MojoFailureException, ProjectBuildingException {
        boolean mustRebuildJar = false;
        String exitOnFile = System.getProperty(TEST_PROPERTY_EXIT);
        try {
            for (;;) {
                WatchKey key = watcher.take();
                BootableAppEventHandler handler = ctx.newEventHandler();
                for (WatchEvent event : key.pollEvents()) {
                    WatchEvent.Kind kind = event.kind();
                    if (kind == OVERFLOW) {
                        continue;
                    }
                    @SuppressWarnings("unchecked")
                    WatchEvent ev = (WatchEvent) event;
                    getLog().debug("[WATCH] file change [" + ev.kind().name() + "]: " + ev.context());
                    if (exitOnFile != null && exitOnFile.equals(ev.context().getFileName().toString())) {
                        getLog().info("Asked to exit by the test");
                        return;
                    }
                    Path absolutePath = ctx.getPath(key, ev.context());
                    if (absolutePath == null) {
                        continue;
                    }
                    getLog().debug("[WATCH] file change [" + ev.kind().name() + "]: " + absolutePath);
                    try {
                        handler.handle(ev.kind(), absolutePath);
                    } catch (Exception ex) {
                        getLog().error("[WATCH], exception handling file change: " + ex);
                    }
                }

                try {
                    if (handler.rebuildBootableJAR || mustRebuildJar) {
                        // We must first stop the server, on Windows platform
                        // we can't rebuild a Bootable JAR although the server is running.
                        getLog().info("[WATCH] stopping bootable JAR");
                        shutdownContainer();
                        getLog().info("[WATCH] server stopped");
                        // Must rebuild the bootable JAR.
                        System.setProperty(REBUILD_MARKER, "true");
                        getLog().info("[WATCH] re-building bootable JAR");
                        try {
                            ctx = triggerRebuildBootableJar(watcher, ctx);
                            mustRebuildJar = false;
                        } catch (Exception ex) {
                            // We are not able to rebuild the server, force rebuilding it
                            // for the next event.
                            mustRebuildJar = true;
                            throw ex;
                        }
                        // We were able to rebuild a bootable JAR
                        // can stop the server

                        handler = ctx.newEventHandler();
                        ctx.build(false);
                        process = Launcher.of(buildCommandBuilder(false))
                                .inherit()
                                .launch();
                        deploymentController.deploy(ctx.getTargetDirectory());
                        getLog().info("[WATCH] server re-started");
                    } else {
                        if (handler.reset) {
                            ctx = resetWatcher(watcher, ctx);
                            handler = ctx.newEventHandler();
                            ctx.build(false);
                        } else {
                            handler.applyChanges();
                        }
                    }
                } catch (Exception ex) {
                    getLog().error("Error rebuilding: " + ex);
                    Throwable cause = ex.getCause();
                    if (cause instanceof ProvisioningException) {
                        getLog().error(cause.getLocalizedMessage());
                    }
                    if (getLog().isDebugEnabled()) {
                        ex.printStackTrace();
                    }
                }
                key.reset();
            }
        } catch (ClosedWatchServiceException ex) {
            // OK Can ignore, we have been closed by shutdown hook.
        } finally {
            watcher.close();
        }
    }

    void handleAutoCompile(MavenProject project) throws MojoExecutionException {
        //we check to see if there was a compile (or later) goal before this plugin
        boolean compileNeeded = true;
        for (String goal : session.getGoals()) {
            if (POST_COMPILE_PHASES.contains(goal)) {
                compileNeeded = false;
                break;
            }
            if (goal.endsWith("wildfly-jar:" + WATCH_GOAL)) {
                break;
            }
        }

        //if the user did not compile we run it for them
        if (compileNeeded) {
            triggerCompile(project);
        }
    }

    void triggerCompile(MavenProject project) throws MojoExecutionException {
        // Compile the Java sources if needed
        final String compilerPluginKey = ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_COMPILER_PLUGIN;
        final Plugin compilerPlugin = project.getPlugin(compilerPluginKey);
        if (compilerPlugin != null) {
            executeGoal(project, compilerPlugin, ORG_APACHE_MAVEN_PLUGINS, MAVEN_COMPILER_PLUGIN, MAVEN_COMPILER_GOAL,
                    getPluginConfig(compilerPlugin, MAVEN_COMPILER_GOAL));
        }
    }

    void triggerExplodeWar(MavenProject project, Path targetDir) throws MojoExecutionException {
        // Compile the Java sources if needed
        final String warPluginKey = ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_WAR_PLUGIN;
        final Plugin warPlugin = project.getPlugin(warPluginKey);
        if (warPlugin != null) {
            executeGoal(project, warPlugin, ORG_APACHE_MAVEN_PLUGINS, MAVEN_WAR_PLUGIN, MAVEN_EXPLODED_GOAL, getPluginConfig(warPlugin, targetDir));
        } else {
            getLog().warn("Can't package war application, war plugin not found");
        }
    }

    void triggerJar(MavenProject project) throws MojoExecutionException {
        // Package as a jar
        final String ejbPluginKey = ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_EJB_PLUGIN;
        final Plugin ejbPlugin = project.getPlugin(ejbPluginKey);
        if (ejbPlugin != null) {
            executeGoal(project, ejbPlugin, ORG_APACHE_MAVEN_PLUGINS, MAVEN_EJB_PLUGIN, MAVEN_EJB_GOAL,
                    getPluginConfig(ejbPlugin, MAVEN_EJB_GOAL));
        } else {
            final String jarPluginKey = ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_JAR_PLUGIN;
            final Plugin jarPlugin = project.getPlugin(jarPluginKey);
            if (jarPlugin != null) {
                executeGoal(project, jarPlugin, ORG_APACHE_MAVEN_PLUGINS, MAVEN_JAR_PLUGIN, MAVEN_JAR_GOAL,
                        getPluginConfig(jarPlugin, MAVEN_JAR_GOAL));
            } else {
                getLog().warn("Can't package jar application, jar nor ejb plugins found");
            }
        }
    }

    Plugin getPlugin(MavenProject project) {
        final String jarPluginKey = MAVEN_WILDFLY_JAR_PLUGINS + ":" + MAVEN_WILDFLY_JAR_PLUGIN;
        return project.getPlugin(jarPluginKey);
    }

    MavenProject newProject(Path pomFile) throws ProjectBuildingException {
        ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
        buildingRequest.setResolveDependencies(true);
        return projectBuilder.build(pomFile.toFile(), buildingRequest).getProject();
    }

    private DevWatchContext resetWatcher(WatchService watcher, DevWatchContext ctx) throws MojoExecutionException, ProjectBuildingException, IOException {
        MavenProject mavenProject = newProject(project.getBasedir().toPath().resolve("pom.xml"));
        updateSession(mavenProject);
        ctx.cleanup();
        ProjectContext projectContext = new ProjectContextImpl(mavenProject,
                (Xpp3Dom) getPlugin(mavenProject).getConfiguration(),
                Paths.get(projectBuildDir), sourceDir.toPath(), contextRoot, cliSessions, extraServerContentDirs);
        return new DevWatchContext(projectContext, watcher);
    }

    private void updateSession(MavenProject newProject) {
        session.setCurrentProject(newProject);
        List lst = new ArrayList<>();
        lst.add(newProject);
        session.setAllProjects(lst);
        session.setProjects(lst);
        Map map = new HashMap<>();
        map.put(newProject.getGroupId() + ":" + newProject.getArtifactId() + ":" + newProject.getVersion(), newProject);
        session.setProjectMap(map);
    }

    private DevWatchContext triggerRebuildBootableJar(WatchService watcher, DevWatchContext ctx) throws MojoExecutionException, ProjectBuildingException, IOException {
        MavenProject mavenProject = newProject(project.getBasedir().toPath().resolve("pom.xml"));
        updateSession(mavenProject);
        final Plugin jarPlugin = getPlugin(mavenProject);
        Path updatedSrcDir = sourceDir.toPath();
        Path updatedBuildDir = Paths.get(projectBuildDir);
        boolean updatedContextRoot = true;
        List updatedExtras = new ArrayList<>();
        List updatedCliSessions = new ArrayList<>();
        if (jarPlugin != null) {
            Xpp3Dom config = getBootableJarPluginConfig(jarPlugin);
            executeGoal(mavenProject, jarPlugin, MAVEN_WILDFLY_JAR_PLUGINS, MAVEN_WILDFLY_JAR_PLUGIN, WATCH_GOAL, config);

            // Resync the jvmArguments and arguments that we are going to re-use when launching the server
            Xpp3Dom jvmArguments = config.getChild("jvmArguments");
            this.jvmArguments.clear();
            if (jvmArguments != null) {
                //rebuild them.
                if (jvmArguments.getChildren() != null && jvmArguments.getChildren().length != 0) {
                    for (Xpp3Dom child : jvmArguments.getChildren()) {
                        this.jvmArguments.add(child.getValue());
                    }
                } else {
                    String value = resolve(jvmArguments.getValue());
                    if (value != null) {
                        this.jvmArguments.addAll(Utils.splitArguments(value));
                    }
                }
            }
            Xpp3Dom serverArguments = config.getChild("arguments");
            this.arguments.clear();
            if (serverArguments != null) {
                //rebuild them.
                if (serverArguments.getChildren() != null && serverArguments.getChildren().length != 0) {
                    for (Xpp3Dom child : serverArguments.getChildren()) {
                        this.arguments.add(child.getValue());
                    }
                } else {
                    String value = resolve(serverArguments.getValue());
                    if (value != null) {
                        this.arguments.addAll(Utils.splitArguments(value));
                    }
                }
            }
            Xpp3Dom srcDir = config.getChild("sourceDir");
            if (srcDir != null) {
                // Is null for defaultValue.
                String value = resolve(srcDir.getValue());
                if (value != null) {
                    updatedSrcDir = Paths.get(value);
                }
            }
            Xpp3Dom projectBuildDir = config.getChild("projectBuildDir");
            if (projectBuildDir != null) {
                // Is null for defaultValue.
                String value = resolve(projectBuildDir.getValue());
                if (value != null) {
                    updatedBuildDir = Paths.get(value);
                }
            }

            Xpp3Dom extra = config.getChild("extraServerContentDirs");
            if (extra != null) {
                if (extra.getChildren() != null && extra.getChildren().length != 0) {
                    for (Xpp3Dom child : extra.getChildren()) {
                        updatedExtras.add(child.getValue());
                    }
                } else {
                    String value = resolve(extra.getValue());
                    if (value != null) {
                        updatedExtras.addAll(Utils.splitArguments(value));
                    }
                }
            }

            Xpp3Dom cli = config.getChild("cliSessions");
            if (cli != null) {
                for (Xpp3Dom child : cli.getChildren()) {
                    CliSession session = new CliSession();
                    Xpp3Dom props = child.getChild("properties-file");
                    if (props == null) {
                        props = child.getChild("propertiesFile");
                    }
                    if (props != null) {
                        session.setPropertiesFile(props.getValue());
                    }
                    Xpp3Dom scripts = child.getChild("script-files");
                    if (scripts == null) {
                        scripts = child.getChild("scriptFiles");
                    }
                    if (scripts != null) {
                        List lst = new ArrayList<>();
                        for (Xpp3Dom script : scripts.getChildren()) {
                            lst.add(script.getValue());
                        }
                        session.setScriptFiles(lst);
                    }
                    updatedCliSessions.add(session);
                }
            }
            Xpp3Dom ctxRoot = config.getChild("contextRoot");
            if (ctxRoot != null) {
                // Is null for defaultValue.
                String value = resolve(ctxRoot.getValue());
                if (value != null) {
                    updatedContextRoot = Boolean.valueOf(value);
                }
            }
            Xpp3Dom hostname = config.getChild("hostname");
            if (hostname != null) {
                // Is null for defaultValue.
                String value = resolve(hostname.getValue());
                if (value != null) {
                    this.hostname = value;
                }
            }
            Xpp3Dom port = config.getChild("port");
            if (port != null) {
                // Is null for defaultValue.
                String value = resolve(port.getValue());
                if (value != null) {
                    this.port = Integer.parseInt(value);
                }
            }

            Xpp3Dom timeout = config.getChild("timeout");
            if (timeout != null) {
                // Is null for defaultValue.
                String value = resolve(timeout.getValue());
                if (value != null) {
                    this.timeout = Integer.parseInt(value);
                }
            }

            Xpp3Dom debug = config.getChild("debug");
            this.debug = false;
            if (debug != null) {
                // Is null for defaultValue.
                String value = resolve(debug.getValue());
                if (value != null) {
                    this.debug = Boolean.parseBoolean(value);
                }
            }

            Xpp3Dom debugPort = config.getChild("debugPort");
            this.debugPort = 8787;
            if (debugPort != null) {
                // Is null for defaultValue.
                String value = resolve(debugPort.getValue());
                if (value != null) {
                    this.debugPort = Integer.parseInt(value);
                }
            }

            Xpp3Dom debugSuspend = config.getChild("debugSuspend");
            this.debugSuspend = false;
            if (debugSuspend != null) {
                // Is null for defaultValue.
                String value = resolve(debugSuspend.getValue());
                if (value != null) {
                    this.debugSuspend = Boolean.parseBoolean(value);
                }
            }
        }
        ctx.cleanup();
        ProjectContext projectContext = new ProjectContextImpl(mavenProject,
                (Xpp3Dom) getPlugin(mavenProject).getConfiguration(),
                updatedBuildDir, updatedSrcDir, updatedContextRoot, updatedCliSessions, updatedExtras);
        return new DevWatchContext(projectContext, watcher);
    }

    private String resolve(String value) {
        if (value != null) {
            if (value.startsWith("${")) {
                String systemProp = value.substring(2, value.length() - 1);
                value = System.getProperty(systemProp);
            }
        }
        return value;
    }

    private void executeGoal(MavenProject project, Plugin plugin, String groupId, String artifactId, String goal, Xpp3Dom config) throws MojoExecutionException {
        executeMojo(
                plugin(
                        groupId(groupId),
                        artifactId(artifactId),
                        version(plugin.getVersion()),
                        plugin.getDependencies()),
                goal(goal),
                config,
                executionEnvironment(
                        project,
                        session,
                        pluginManager));
    }

    void triggerResources(MavenProject project) throws MojoExecutionException {
        List resources = project.getResources();
        if (resources.isEmpty()) {
            return;
        }
        Plugin resourcesPlugin = project.getPlugin(ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_RESOURCES_PLUGIN);
        if (resourcesPlugin == null) {
            return;
        }
        executeGoal(project, resourcesPlugin, ORG_APACHE_MAVEN_PLUGINS, MAVEN_RESOURCES_PLUGIN, MAVEN_RESOURCES_GOAL,
                getPluginConfig(resourcesPlugin, MAVEN_RESOURCES_GOAL));
    }

    void cleanClasses(MavenProject project) throws MojoExecutionException {
        Path buildDir = Paths.get(this.projectBuildDir);
        IoUtils.recursiveDelete(Paths.get(project.getBuild().getOutputDirectory()));
        final String compilerPluginKey = ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_COMPILER_PLUGIN;
        final Plugin compilerPlugin = project.getPlugin(compilerPluginKey);
        if (compilerPlugin != null) {
            Path p;
            Xpp3Dom config = getPluginConfig(compilerPlugin, MAVEN_COMPILER_GOAL);
            Xpp3Dom genSources = config.getChild("generatedSourcesDirectory");
            if (genSources == null) {
                p = buildDir.resolve("generated-sources").resolve("annotations");
            } else {
                String path = genSources.getValue();
                p = Paths.get(path);
            }
            IoUtils.recursiveDelete(p);
        }
    }

    private Xpp3Dom getPluginConfig(Plugin plugin, Path target) {

        Xpp3Dom configuration = configuration();

        Xpp3Dom pluginConfiguration = (Xpp3Dom) plugin.getConfiguration();
        if (pluginConfiguration != null) {
            //Filter out `test*` configurations
            for (Xpp3Dom child : pluginConfiguration.getChildren()) {
                if (!child.getName().startsWith("test") && !child.getName().startsWith("failOnMissingWebXml")) {
                    configuration.addChild(child);
                }
            }
        }
        MojoExecutor.Element e = new MojoExecutor.Element("webappDirectory", target.toAbsolutePath().toString());
        configuration.addChild(e.toDom());
        return configuration;
    }

    private Xpp3Dom getBootableJarPluginConfig(Plugin plugin) {

        Xpp3Dom configuration = configuration();

        Xpp3Dom pluginConfiguration = (Xpp3Dom) plugin.getConfiguration();
        if (pluginConfiguration != null) {
            for (Xpp3Dom child : pluginConfiguration.getChildren()) {
                String camelName = camelize(child.getName());
                Xpp3Dom dom = new Xpp3Dom(camelName);
                if (child.getValue() != null) {
                    dom.setValue(child.getValue());
                }
                for (String attribute : child.getAttributeNames()) {
                    dom.setAttribute(attribute, child.getAttribute(attribute));
                }
                for (Xpp3Dom d : child.getChildren()) {
                    dom.addChild(d);
                }
                configuration.addChild(dom);
            }
        }

        return configuration;
    }

    private Xpp3Dom getPluginConfig(Plugin plugin, String goal) throws MojoExecutionException {
        Xpp3Dom mergedConfig = null;
        if (!plugin.getExecutions().isEmpty()) {
            for (PluginExecution exec : plugin.getExecutions()) {
                if (exec.getConfiguration() != null && exec.getGoals().contains(goal)) {
                    mergedConfig = mergedConfig == null ? (Xpp3Dom) exec.getConfiguration()
                            : Xpp3Dom.mergeXpp3Dom(mergedConfig, (Xpp3Dom) exec.getConfiguration(), true);
                }
            }
        }

        if ((Xpp3Dom) plugin.getConfiguration() != null) {
            mergedConfig = mergedConfig == null ? (Xpp3Dom) plugin.getConfiguration()
                    : Xpp3Dom.mergeXpp3Dom(mergedConfig, (Xpp3Dom) plugin.getConfiguration(), true);
        }

        final Xpp3Dom configuration = configuration();

        if (mergedConfig != null) {
            Set supportedParams = null;
            // Filter out `test*` configurations
            for (Xpp3Dom child : mergedConfig.getChildren()) {
                if (child.getName().startsWith("test")) {
                    continue;
                }
                if (supportedParams == null) {
                    supportedParams = getMojoDescriptor(plugin, goal).getParameterMap().keySet();
                }
                if (supportedParams.contains(child.getName())) {
                    configuration.addChild(child);
                }
            }
        }

        return configuration;
    }

    // Required to retrieve the actual set of supported configuration items.
    private MojoDescriptor getMojoDescriptor(Plugin plugin, String goal) throws MojoExecutionException {
        try {
            return pluginManager.getMojoDescriptor(plugin, goal, pluginRepos, repoSession);
        } catch (Exception e) {
            throw new MojoExecutionException(
                    "Failed to obtain descriptor for Maven plugin " + plugin.getId() + " goal " + goal, e);
        }
    }

    private ModelControllerClient createClient() throws UnknownHostException {
        return ModelControllerClient.Factory.create(hostname, port);
    }

    private void shutdownContainer() {
        if (process != null) {
            if (process.isAlive()) {
                // Attempt to safely shutdown first
                try (ModelControllerClient client = createClient()) {
                    ServerHelper.shutdownStandalone(client, timeout);
                } catch (Throwable ignore) {
                    process.destroy();
                }
                try {
                    if (!process.waitFor(timeout, TimeUnit.SECONDS)) {
                        process.destroyForcibly();
                    }
                } catch (InterruptedException ex) {
                    getLog().error("Error waiting for process to terminate " + ex);
                }
            }
            process = null;
            // Make a timed attempt to wait for the directory to be deleted. In some cases the delete may still be
            // happening and the JAR may be in use on Windows.
            if (currentServerDir != null && Files.exists(currentServerDir.resolve("wildfly-cleanup-marker"))) {
                try {
                    int timeout = this.timeout * 1000;
                    while (Files.exists(currentServerDir)) {
                        TimeUnit.MILLISECONDS.sleep(500L);
                        timeout -= 500;
                        if (timeout <= 0) {
                            getLog().warn(String.format("Failed to wait for server directory to be deleted: %s", currentServerDir));
                            break;
                        }
                    }
                } catch (InterruptedException ignore) {
                } finally {
                    currentServerDir = null;
                }
            }
        }
    }

    private Path getHomeDirectory(final ModelControllerClient client) throws IOException {
        final ModelNode op = Operations.createReadAttributeOperation(
                Operations.createAddress("core-service", "server-environment"), "home-dir");
        final ModelNode result = client.execute(op);
        if (Operations.isSuccessfulOutcome(result)) {
            return Paths.get(Operations.readResult(result).asString());
        }
        getLog().warn(String.format("Failed to find home directory: %s", Operations.getFailureDescription(result).asString()));
        return null;
    }

    private static String camelize(final String name) {
        StringBuilder buf = null;

        final int length = name.length();
        for (int i = 0; i < length; i++) {
            if ('-' == name.charAt(i)) {
                buf = new StringBuilder(name.substring(0, i));
                break;
            }
        }

        if (buf == null) {
            return name;
        }

        boolean capitalize = true;
        for (int i = buf.length() + 1; i < length; i++) {
            final char c = name.charAt(i);
            if ('-' == c) {
                capitalize = true;
            } else if (capitalize) {
                buf.append(Character.toTitleCase(c));
                capitalize = false;
            } else {
                buf.append(c);
            }
        }

        return buf.toString();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy