diff --git a/plugin/pom.xml b/plugin/pom.xml index 4ed944d9..674990ae 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -156,6 +156,12 @@ org.jboss.logmanager jboss-logmanager + + + org.twdata.maven + mojo-executor + ${version.org.twdata.maven} + org.wildfly.core wildfly-controller-client diff --git a/plugin/src/main/java/org/wildfly/plugin/common/Environment.java b/plugin/src/main/java/org/wildfly/plugin/common/Environment.java index 9a4e8235..0f61a8fc 100644 --- a/plugin/src/main/java/org/wildfly/plugin/common/Environment.java +++ b/plugin/src/main/java/org/wildfly/plugin/common/Environment.java @@ -138,6 +138,15 @@ public static String getJavaCommand(final Path javaHome) { return exe; } + /** + * Determines if this is a Windows environment. + * + * @return {@code true} if this is a Windows environment, otherwise {@code false} + */ + public static boolean isWindows() { + return WINDOWS; + } + private static Path findJavaHome() { String path = WildFlySecurityManager.getPropertyPrivileged("java.home", null); if (path != null) { diff --git a/plugin/src/main/java/org/wildfly/plugin/common/Utils.java b/plugin/src/main/java/org/wildfly/plugin/common/Utils.java index 3908f5f2..22df57fa 100644 --- a/plugin/src/main/java/org/wildfly/plugin/common/Utils.java +++ b/plugin/src/main/java/org/wildfly/plugin/common/Utils.java @@ -22,7 +22,10 @@ package org.wildfly.plugin.common; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -33,6 +36,8 @@ public class Utils { private static final Pattern EMPTY_STRING = Pattern.compile("^$|\\s+"); + private static final Pattern WHITESPACE_IF_NOT_QUOTED = Pattern.compile("(\\S+\"[^\"]+\")|\\S+"); + public static final String WILDFLY_DEFAULT_DIR= "server"; /** * Tests if the character sequence is not {@code null} and not empty. @@ -75,4 +80,24 @@ public static String toString(final Iterable iterable, final CharSequence del } return result.toString(); } + + /** + * Splits the arguments into a list. The arguments are split based on whitespace while ignoring whitespace that is + * within quotes. + * + * @param arguments the arguments to split + * + * @return the list of the arguments + */ + public static List splitArguments(final CharSequence arguments) { + final List args = new ArrayList<>(); + final Matcher m = WHITESPACE_IF_NOT_QUOTED.matcher(arguments); + while (m.find()) { + final String value = m.group(); + if (!value.isEmpty()) { + args.add(value); + } + } + return args; + } } diff --git a/plugin/src/main/java/org/wildfly/plugin/dev/CompiledSourceHandler.java b/plugin/src/main/java/org/wildfly/plugin/dev/CompiledSourceHandler.java new file mode 100644 index 00000000..f5809e21 --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/dev/CompiledSourceHandler.java @@ -0,0 +1,52 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.dev; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.WatchEvent; + +import org.apache.maven.plugin.MojoExecutionException; + +/** + * @author James R. Perkins + */ +class CompiledSourceHandler implements WatchHandler { + + @Override + public Result handle(final WatchContext context, final WatchEvent event, final Path file) throws IOException, MojoExecutionException { + return new Result() { + @Override + public boolean requiresRecompile() { + return true; + } + + @Override + public boolean requiresRedeploy() { + return true; + } + + @Override + public boolean requiresRepackage() { + return true; + } + }; + } +} diff --git a/plugin/src/main/java/org/wildfly/plugin/dev/DevMojo.java b/plugin/src/main/java/org/wildfly/plugin/dev/DevMojo.java new file mode 100644 index 00000000..383e37cf --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/dev/DevMojo.java @@ -0,0 +1,618 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.dev; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.nio.file.StandardWatchEventKinds.OVERFLOW; +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.groupId; +import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin; +import static org.twdata.maven.mojoexecutor.MojoExecutor.version; + +import java.io.File; +import java.io.IOException; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.DosFileAttributes; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.inject.Inject; + +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.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.descriptor.MojoDescriptor; +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.MavenProject; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.jboss.as.controller.client.ModelControllerClient; +import org.twdata.maven.mojoexecutor.MojoExecutor; +import org.wildfly.core.launcher.CommandBuilder; +import org.wildfly.plugin.cli.CommandConfiguration; +import org.wildfly.plugin.cli.CommandExecutor; +import org.wildfly.plugin.common.Environment; +import org.wildfly.plugin.common.PropertyNames; +import org.wildfly.plugin.common.Utils; +import org.wildfly.plugin.core.Deployment; +import org.wildfly.plugin.core.DeploymentManager; +import org.wildfly.plugin.core.DeploymentResult; +import org.wildfly.plugin.core.ServerHelper; +import org.wildfly.plugin.core.UndeployDescription; +import org.wildfly.plugin.deployment.PackageType; +import org.wildfly.plugin.server.AbstractServerStartMojo; +import org.wildfly.plugin.server.ServerContext; +import org.wildfly.plugin.server.ServerType; + +/** + * Starts a standalone instance of WildFly and deploys the application to the server. The deployment type myst be a WAR. + * Once the server is running, the source directories are monitored for changes. If required the sources will be compiled + * and the deployment may be redeployed. + * + *

+ * Note that changes to the POM file are not monitored. If changes are made the POM file, the process will need to be + * terminated and restarted. + *

+ * + * @author James R. Perkins + * @since 4.1 + */ +@Mojo(name = "dev", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.PACKAGE) +public class DevMojo extends AbstractServerStartMojo { + 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_RESOURCES_PLUGIN = "maven-resources-plugin"; + private static final String MAVEN_RESOURCES_GOAL = "resources"; + + /** + * Executing 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 = Set.of( + "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 final Map watchedDirectories = new HashMap<>(); + + @Component + private BuildPluginManager pluginManager; + + @Inject + private CommandExecutor commandExecutor; + + /** + * The CLI commands to execute before the deployment is deployed. + */ + @Parameter(property = PropertyNames.COMMANDS) + private List commands = new ArrayList<>(); + + /** + * The CLI script files to execute before the deployment is deployed. + */ + @Parameter(property = PropertyNames.SCRIPTS) + private List scripts = new ArrayList<>(); + + /** + * The path to the server configuration to use. + */ + @Parameter(alias = "server-config", property = PropertyNames.SERVER_CONFIG) + private String serverConfig; + + /** + * Additional extensions of files located in {@code src/webapp} directory and its sub-directories that don't + * require a redeployment on update. + *

+ * The builtin list is {@code html, xhtml, jsp, js, css}. + *

+ *

+ * You can set the system property {@code wildfly.dev.web.extensions} to a white space separated list of file + * extensions. + *

+ */ + @Parameter(property = "wildfly.dev.web.extensions", alias = "web-extensions") + private List webExtensions = new ArrayList<>(); + + /** + * File patterns that we should ignore during watch. + *

+ * Hidden files and files ending with '~' are ignored. + *

+ *

+ * You can set the system property {@code wildfly.dev.ignore.patterns} to a white space separated list of file + * patterns. + *

+ */ + @Parameter(property = "wildfly.dev.ignore.patterns", alias = "ignore-patterns") + private List ignorePatterns = new ArrayList<>(); + // Lazily loaded list of patterns based on the ignorePatterns + private final List ignoreUpdatePatterns = new ArrayList<>(); + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + final var packageType = PackageType.resolve(project); + if (!"war".equals(packageType.getPackaging())) { + throw new MojoExecutionException("The dev goal only works for WAR deployments"); + } + // Start the container + final ServerContext context = startServer(ServerType.STANDALONE); + // Do we need to build first? + if (needsCompile()) { + triggerResources(); + triggerCompile(); + triggerExplodeWar(); + } + try (final WatchService watcher = FileSystems.getDefault().newWatchService()) { + final CompiledSourceHandler sourceHandler = new CompiledSourceHandler(); + registerDir(watcher, Path.of(project.getBuild().getSourceDirectory()), sourceHandler); + for (Resource resource : project.getResources()) { + registerDir(watcher, Path.of(resource.getDirectory()), new ResourceHandler()); + } + registerDir(watcher, resolveWebAppSourceDir(), new WebAppResourceHandler(webExtensions)); + try (ModelControllerClient client = createClient()) { + // Execute commands before the deployment is done + final CommandConfiguration cmdConfig = CommandConfiguration.of(this::createClient, this::getClientConfiguration) + .addCommands(commands) + .addScripts(scripts) + .setJBossHome(context.jbossHome()) + .setFork(true) + .setStdout("none") + .setTimeout(timeout) + .build(); + commandExecutor.execute(cmdConfig, mavenRepoManager); + final DeploymentManager deploymentManager = DeploymentManager.Factory.create(client); + final Deployment deployment = getDeploymentContent(); + try { + final DeploymentResult result = deploymentManager.forceDeploy(deployment); + if (!result.successful()) { + throw new MojoExecutionException("Failed to deploy content: " + result.getFailureMessage()); + } + watch(watcher, deploymentManager, deployment); + } finally { + deploymentManager.undeploy(UndeployDescription.of(deployment)); + ServerHelper.shutdownStandalone(client); + } + } + } catch (IOException e) { + throw new MojoExecutionException(e.getLocalizedMessage(), e); + } finally { + context.process().destroyForcibly(); + } + } + + @Override + public String goal() { + return "dev"; + } + + @Override + protected CommandBuilder createCommandBuilder(final Path jbossHome) throws MojoExecutionException { + return createStandaloneCommandBuilder(jbossHome, serverConfig); + } + + /** + * Allows the {@linkplain #webExtensions} to be set as a string. + * + * @param webExtensions a whitespace delimited string for the web file extensions + */ + @SuppressWarnings("unused") + public void setWebExtensions(final String webExtensions) { + this.webExtensions = Utils.splitArguments(webExtensions); + } + + /** + * Allows the {@linkplain #ignorePatterns} to be set as a string. + * + * @param ignorePatterns a whitespace delimited string for the file patterns + */ + @SuppressWarnings("unused") + public void setIgnorePatterns(final String ignorePatterns) { + this.ignorePatterns = Utils.splitArguments(ignorePatterns); + } + + private boolean registerDir(final WatchService watcher, final Path dir, final WatchHandler handler) throws IOException { + if (Files.exists(dir) && Files.isDirectory(dir)) { + final int currentSize = watchedDirectories.size(); + final Set registered = watchedDirectories.values() + .stream() + .map(WatchContext::directory) + .collect(Collectors.toCollection(HashSet::new)); + Files.walkFileTree(dir, new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (!project.getBuild().getOutputDirectory().equals(dir.toString())) { + if (registered.add(dir)) { + final WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + watchedDirectories.put(key, WatchContext.of(dir, handler)); + debug("Watching for changes in %s", dir); + } + return FileVisitResult.CONTINUE; + } else { + return FileVisitResult.SKIP_SUBTREE; + } + } + }); + return currentSize == watchedDirectories.size(); + } + return false; + } + + @SuppressWarnings("InfiniteLoopStatement") + private void watch(final WatchService watcher, final DeploymentManager deploymentManager, final Deployment deployment) { + final var projectDir = project.getBasedir().toPath(); + try { + for (; ; ) { + WatchKey key = watcher.take(); + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + if (kind == OVERFLOW) { + continue; + } + @SuppressWarnings("unchecked") + final WatchEvent ev = (WatchEvent) event; + debug("File changed [%s]: %s", ev.kind().name(), ev.context()); + final Path absolutePath = getPath(key, ev.context()); + if (absolutePath == null) { + continue; + } + final var eventPath = absolutePath.getFileName(); + try { + if (isIgnoredChange(eventPath)) { + debug("Ignoring change for %s", eventPath); + continue; + } + } catch (IOException ex) { + debug("Failed checking %s for ignored state: %s", eventPath, ex); + } + try { + final var context = watchedDirectories.get(key); + if (context == null) { + getLog().warn(String.format("Failed to find context for %s", ev.context())); + continue; + } + final var relativePath = projectDir.relativize(absolutePath); + if (ev.kind() == ENTRY_DELETE) { + // Undeploy application as Windows won't be able to delete the directory + DeploymentResult deploymentResult = deploymentManager.undeploy(UndeployDescription.of(deployment)); + if (!deploymentResult.successful()) { + getLog().warn(String.format("Failed to undeploy application. Unexpected results may occur. Failure: %s", + deploymentResult.getFailureMessage())); + } else { + // Clean the deployment directory + final Path path = resolveWarDir(); + deleteRecursively(path); + triggerResources(); + triggerCompile(); + triggerExplodeWar(); + deploymentResult = deploymentManager.deploy(deployment); + if (!deploymentResult.successful()) { + throw new MojoExecutionException("Failed to deploy content: " + deploymentResult.getFailureMessage()); + } + if (Files.notExists(context.directory())) { + watchedDirectories.remove(key); + key.cancel(); + } + continue; + } + } else if (ev.kind() == ENTRY_CREATE) { + // If this is a directory we need to add the directory + if (Files.isDirectory(eventPath)) { + if (registerDir(watcher, eventPath, context.handler())) { + debug("New directory registered: %s", relativePath); + } + } else { + final Path parent = absolutePath.getParent(); + if (parent != null) { + if (registerDir(watcher, parent, context.handler())) { + debug("New directory registered: %s", relativePath); + } + } + debug("A new source file has been created: %s", relativePath); + } + } else if (ev.kind() == ENTRY_MODIFY) { + debug("Source file modified: %s", relativePath); + } + // Handle the file + final var result = context.handle(ev, absolutePath); + if (result.requiresRecompile()) { + triggerCompile(); + } + if (result.requiresCopyResources()) { + triggerResources(); + } + if (result.requiresRepackage()) { + triggerExplodeWar(); + } + if (result.requiresRedeploy()) { + final DeploymentResult deploymentResult = deploymentManager.redeployToRuntime(deployment); + if (!deploymentResult.successful()) { + throw new MojoExecutionException("Failed to deploy content: " + deploymentResult.getFailureMessage()); + } + } + } catch (Exception ex) { + getLog().error("Exception handling file change: " + ex); + } + } + key.reset(); + } + } catch (ClosedWatchServiceException ex) { + // OK Can ignore, we have been closed by shutdown hook. + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted during watch.", e); + } + } + + private void triggerCompile() 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)); + } + } + + private void triggerExplodeWar() 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)); + } else { + getLog().warn("Can't package war application, war plugin not found"); + } + } + + private void triggerResources() 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)); + } + + private Path getPath(final WatchKey key, final Path fileName) { + final WatchContext context = watchedDirectories.get(key); + if (context == null) { + getLog().debug("No more watching key, ignoring change done to " + fileName); + return null; + } else { + final Path resolved = context.directory().resolve(fileName); + // Fully ignore target dir + if (Path.of(project.getBuild().getDirectory()).equals(resolved)) { + return null; + } + return resolved; + } + + } + + private boolean needsCompile() { + // Check if compiling is going to be done by a previous goal + boolean compileNeeded = true; + for (String goal : mavenSession.getGoals()) { + if (POST_COMPILE_PHASES.contains(goal)) { + compileNeeded = false; + break; + } + if (goal.endsWith("wildfly:" + goal())) { + break; + } + } + return compileNeeded; + } + + private void executeGoal(final MavenProject project, final Plugin plugin, final String groupId, final String artifactId, final String goal, final Xpp3Dom config) throws MojoExecutionException { + executeMojo(plugin(groupId(groupId), artifactId(artifactId), version(plugin.getVersion()), plugin.getDependencies()), MojoExecutor.goal(goal), config, executionEnvironment(project, mavenSession, pluginManager)); + } + + private Xpp3Dom getPluginConfig(final Plugin plugin, final 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 (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; + } + + private Xpp3Dom getPluginConfig(final Plugin plugin) { + + final Xpp3Dom configuration = configuration(); + + final 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); + } + } + } + + final MojoExecutor.Element e = new MojoExecutor.Element("webappDirectory", resolveWarDir().toAbsolutePath() + .toString()); + configuration.addChild(e.toDom()); + 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, repositories, session); + } catch (Exception e) { + throw new MojoExecutionException("Failed to obtain descriptor for Maven plugin " + plugin.getId() + " goal " + goal, e); + } + } + + private boolean isIgnoredChange(final Path file) throws IOException { + if (isHiddenFile(file) || file.getFileName().toString().endsWith("~")) { + return true; + } + for (Pattern pattern : getPatterns()) { + if (pattern.matcher(file.getFileName().toString()).matches()) { + return true; + } + } + return false; + } + + private boolean isHiddenFile(final Path p) throws IOException { + if (Environment.isWindows()) { + final DosFileAttributes dosAttrs = Files.readAttributes(p, DosFileAttributes.class); + return dosAttrs.isHidden(); + } else { + return Files.isHidden(p); + } + } + + private List getPatterns() { + if (!ignorePatterns.isEmpty()) { + if (ignoreUpdatePatterns.isEmpty()) { + for (String p : ignorePatterns) { + Pattern pattern = Pattern.compile(p); + ignoreUpdatePatterns.add(pattern); + } + } + } + return ignoreUpdatePatterns; + } + + private Deployment getDeploymentContent() { + final PackageType packageType = PackageType.resolve(project); + final Deployment deployment = Deployment.of(resolveWarDir()); + final String filename; + //if (this.filename == null) { + filename = String.format("%s.%s", project.getBuild().getFinalName(), packageType.getFileExtension()); + //} else { + //filename = this.filename; + //} + return deployment.setRuntimeName(filename); + } + + private Path resolveWebAppSourceDir() { + final String warPluginKey = ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_WAR_PLUGIN; + final Plugin warPlugin = project.getPlugin(warPluginKey); + Xpp3Dom dom = getPluginConfig(warPlugin); + final Xpp3Dom warSourceDirectory = dom.getChild("warSourceDirectory"); + if (warSourceDirectory == null) { + return project.getBasedir().toPath().resolve("src").resolve("main").resolve("webapp"); + } + return Path.of(warSourceDirectory.getValue()); + } + + private Path resolveWarDir() { + return Path.of(project.getBuild().getDirectory()).resolve(project.getBuild().getFinalName()); + } + + private void debug(final String format, final Object... args) { + getLog().debug(String.format("[WATCH] " + format, args)); + } + + private static void deleteRecursively(final Path path) throws IOException { + if (Files.isDirectory(path)) { + Files.walkFileTree(path, new SimpleFileVisitor<>() { + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } else if (Files.exists(path)) { + Files.delete(path); + } + } +} diff --git a/plugin/src/main/java/org/wildfly/plugin/dev/ResourceHandler.java b/plugin/src/main/java/org/wildfly/plugin/dev/ResourceHandler.java new file mode 100644 index 00000000..fe88349f --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/dev/ResourceHandler.java @@ -0,0 +1,50 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.dev; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.WatchEvent; + +/** + * @author James R. Perkins + */ +class ResourceHandler implements WatchHandler { + + @Override + public Result handle(final WatchContext context, final WatchEvent event, final Path file) throws IOException { + return new Result() { + @Override + public boolean requiresCopyResources() { + return true; + } + + @Override + public boolean requiresRepackage() { + return true; + } + + @Override + public boolean requiresRedeploy() { + return true; + } + }; + } +} diff --git a/plugin/src/main/java/org/wildfly/plugin/dev/WatchContext.java b/plugin/src/main/java/org/wildfly/plugin/dev/WatchContext.java new file mode 100644 index 00000000..f3ef6966 --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/dev/WatchContext.java @@ -0,0 +1,61 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.dev; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.WatchEvent; + +import org.apache.maven.plugin.MojoExecutionException; + +/** + * @author James R. Perkins + */ +class WatchContext implements Comparable { + private final Path directory; + private final WatchHandler handler; + + private WatchContext(final Path directory, final WatchHandler handler) { + this.directory = directory; + this.handler = handler; + } + + static WatchContext of(final Path directory, final WatchHandler handler) { + return new WatchContext(directory, handler); + } + + Path directory() { + return directory; + } + + WatchHandler handler() { + return handler; + } + + // TODO (jrp) should this just go away? + WatchHandler.Result handle(final WatchEvent event, final Path file) throws MojoExecutionException, IOException { + return handler.handle(this, event, directory.resolve(file)); + } + + @Override + public int compareTo(final WatchContext o) { + return directory().compareTo(o.directory()); + } +} diff --git a/plugin/src/main/java/org/wildfly/plugin/dev/WatchHandler.java b/plugin/src/main/java/org/wildfly/plugin/dev/WatchHandler.java new file mode 100644 index 00000000..8ecd58ce --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/dev/WatchHandler.java @@ -0,0 +1,79 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.dev; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.WatchEvent; + +import org.apache.maven.plugin.MojoExecutionException; + +/** + * A handler for changes that happen to source files. + * + * @author James R. Perkins + */ +public interface WatchHandler { + + + Result handle(WatchContext context, WatchEvent event, Path file) throws IOException, MojoExecutionException; + + /** + * The result of handling the changed source file. + */ + interface Result { + + /** + * Indicates whether a recompile is required. + * + * @return {@code true} if a recompile is required + */ + default boolean requiresRecompile() { + return false; + } + + /** + * Indicates whether the deployment should be redeployed is required. + * + * @return {@code true} if the deployment should be redeployed is required + */ + default boolean requiresRedeploy() { + return false; + } + + /** + * Indicates whether the resources should be copied. + * + * @return {@code true} if the resources should be copied + */ + default boolean requiresCopyResources() { + return false; + } + + /** + * Indicates whether the deployment should be repackaged. + * + * @return {@code true} if the deployment should be repackaged + */ + default boolean requiresRepackage() { + return false; + } + } +} diff --git a/plugin/src/main/java/org/wildfly/plugin/dev/WebAppResourceHandler.java b/plugin/src/main/java/org/wildfly/plugin/dev/WebAppResourceHandler.java new file mode 100644 index 00000000..0eba356d --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/dev/WebAppResourceHandler.java @@ -0,0 +1,72 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.dev; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.util.Collection; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author James R. Perkins + */ +class WebAppResourceHandler implements WatchHandler { + private static final Set NO_DEPLOYMENT_WEB_FILE_EXTENSIONS = Set.of( + ".xhtml", + ".html", + ".jsp", + ".js", + ".css" + ); + + private final Set ignoredFileExtensions; + + WebAppResourceHandler(final Collection ignoredFileExtensions) { + this.ignoredFileExtensions = ignoredFileExtensions.stream() + .map((value) -> value.charAt(0) == '.' ? value : "." + value) + .collect(Collectors.toCollection(HashSet::new)); + this.ignoredFileExtensions.addAll(NO_DEPLOYMENT_WEB_FILE_EXTENSIONS); + } + + @Override + public Result handle(final WatchContext context, final WatchEvent event, final Path file) throws IOException { + // Check the file extension to see if a redeploy should be ignored + final String fileName = file.getFileName().toString(); + final int dot = fileName.lastIndexOf('.'); + final boolean requiresRedeploy = dot <= 0 || + !ignoredFileExtensions.contains(fileName.substring(dot).toLowerCase(Locale.ROOT)); + return new Result() { + + @Override + public boolean requiresRepackage() { + return true; + } + + @Override + public boolean requiresRedeploy() { + return requiresRedeploy; + } + }; + } +} diff --git a/plugin/src/main/java/org/wildfly/plugin/server/AbstractServerStartMojo.java b/plugin/src/main/java/org/wildfly/plugin/server/AbstractServerStartMojo.java new file mode 100644 index 00000000..147b6bb2 --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/server/AbstractServerStartMojo.java @@ -0,0 +1,415 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.server; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.repository.RemoteRepository; +import org.jboss.as.controller.client.ModelControllerClient; +import org.jboss.as.controller.client.helpers.domain.DomainClient; +import org.jboss.galleon.ProvisioningException; +import org.jboss.galleon.maven.plugin.util.MavenArtifactRepositoryManager; +import org.jboss.galleon.universe.maven.repo.MavenRepoManager; +import org.wildfly.core.launcher.CommandBuilder; +import org.wildfly.core.launcher.DomainCommandBuilder; +import org.wildfly.core.launcher.Launcher; +import org.wildfly.core.launcher.StandaloneCommandBuilder; +import org.wildfly.plugin.common.AbstractServerConnection; +import org.wildfly.plugin.common.Environment; +import org.wildfly.plugin.common.PropertyNames; +import org.wildfly.plugin.common.StandardOutput; +import org.wildfly.plugin.common.Utils; +import org.wildfly.plugin.core.GalleonUtils; +import org.wildfly.plugin.core.MavenRepositoriesEnricher; +import org.wildfly.plugin.core.ServerHelper; + +/** + * @author James R. Perkins + */ +public abstract class AbstractServerStartMojo extends AbstractServerConnection { + + @Component + protected RepositorySystem repoSystem; + + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true) + protected RepositorySystemSession session; + + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) + protected List repositories; + + @Parameter(defaultValue = "${project}", readonly = true, required = true) + protected MavenProject project; + + @Parameter(defaultValue = "${session}", readonly = true, required = true) + protected MavenSession mavenSession; + /** + * The target directory the application to be deployed is located. + */ + @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true) + private File targetDir; + + /** + * The WildFly Application Server's home directory. If not used, WildFly will be downloaded. + */ + @Parameter(alias = "jboss-home", property = PropertyNames.JBOSS_HOME) + private String jbossHome; + + /** + * The feature pack location. See the documentation + * for details on how to format a feature pack location. + *

+ * Note that if you define the version in the feature pack location, e.g. {@code #26.1.1.Final}, the {@code version} + * configuration parameter should be left blank. + *

+ */ + @Parameter(alias = "feature-pack-location", property = PropertyNames.WILDFLY_FEATURE_PACK_LOCATION) + private String featurePackLocation; + + /** + * The version of the WildFly default server to install in case no jboss-home has been set + * and no server has previously been provisioned. + *

+ * The latest stable version is resolved if left blank. + *

+ */ + @Parameter(property = PropertyNames.WILDFLY_VERSION) + private String version; + + /** + * The directory name inside the buildDir where to provision the default server. + * By default the server is provisioned into the 'server' directory. + * + * @since 3.0 + */ + @Parameter(alias = "provisioning-dir", property = PropertyNames.WILDFLY_PROVISIONING_DIR, defaultValue = Utils.WILDFLY_DEFAULT_DIR) + private String provisioningDir; + + /** + * The modules path or paths to use. A single path can be used or multiple paths by enclosing them in a paths + * element. + */ + @Parameter(alias = "modules-path", property = PropertyNames.MODULES_PATH) + private ModulesPath modulesPath; + + /** + * The JVM options to use. + */ + @Parameter(alias = "java-opts", property = PropertyNames.JAVA_OPTS) + private String[] javaOpts; + + /** + * The {@code JAVA_HOME} to use for launching the server. + */ + @Parameter(alias = "java-home", property = PropertyNames.JAVA_HOME) + private String javaHome; + + /** + * Starts the server with debugging enabled. + */ + @Parameter(property = "wildfly.debug", defaultValue = "false") + private boolean debug; + + /** + * Sets the hostname to listen on for debugging. An {@code *} means all hosts. + */ + @Parameter(property = "wildfly.debug.host", defaultValue = "*") + private String debugHost; + + /** + * Sets the port the debugger should listen on. + */ + @Parameter(property = "wildfly.debug.port", defaultValue = "8787") + private int debugPort; + + /** + * Indicates whether the server should suspend itself until a debugger is attached. + */ + @Parameter(property = "wildfly.debug.suspend", defaultValue = "false") + private boolean debugSuspend; + + /** + * The path to the system properties file to load. + */ + @Parameter(alias = "properties-file", property = PropertyNames.PROPERTIES_FILE) + private String propertiesFile; + + /** + * The timeout value to use when starting the server. + */ + @Parameter(alias = "startup-timeout", defaultValue = "60", property = PropertyNames.STARTUP_TIMEOUT) + private long startupTimeout; + + /** + * The arguments to be passed to the server. + */ + @Parameter(alias = "server-args", property = PropertyNames.SERVER_ARGS) + private String[] serverArgs; + + /** + * Set to {@code true} if you want to skip this goal, otherwise {@code false}. + */ + @Parameter(defaultValue = "false", property = PropertyNames.SKIP) + protected boolean skip; + + /** + * The users to add to the server. + */ + @Parameter(alias = "add-user", property = "wildfly.add-user") + private AddUser addUser; + + /** + * Specifies the environment variables to be passed to the process being started. + *
+ *
+     * <env>
+     *     <HOME>/home/wildfly/</HOME>
+     * </env>
+     * 
+ *
+ */ + @Parameter + private Map env; + + protected MavenRepoManager mavenRepoManager; + + protected ServerContext startServer(final ServerType serverType) throws MojoExecutionException, MojoFailureException { + final Log log = getLog(); + MavenRepositoriesEnricher.enrich(mavenSession, project, repositories); + mavenRepoManager = new MavenArtifactRepositoryManager(repoSystem, session, repositories); + + // Validate the environment + final Path jbossHome = provisionIfRequired(targetDir.toPath().resolve(provisioningDir)); + if (!ServerHelper.isValidHomeDirectory(jbossHome)) { + throw new MojoExecutionException(String.format("JBOSS_HOME '%s' is not a valid directory.", jbossHome)); + } + + // Determine how stdout should be consumed + try { + final StandardOutput out = standardOutput(); + // Create the server and close the client after the start. The process will continue running even after + // the maven process may have been finished + try (ModelControllerClient client = createClient()) { + if (ServerHelper.isStandaloneRunning(client) || ServerHelper.isDomainRunning(client)) { + throw new MojoExecutionException(String.format("%s server is already running?", serverType)); + } + final CommandBuilder commandBuilder = createCommandBuilder(jbossHome); + log.info(String.format("%s server is starting up.", serverType)); + final Launcher launcher = Launcher.of(commandBuilder) + .setRedirectErrorStream(true); + if (env != null) { + launcher.addEnvironmentVariables(env); + } + out.getRedirect().ifPresent(launcher::redirectOutput); + + final Process process = launcher.launch(); + // Note that if this thread is started and no shutdown goal is executed this stop the stdout and stderr + // from being logged any longer. The user was warned in the documentation. + out.startConsumer(process); + if (serverType == ServerType.DOMAIN) { + ServerHelper.waitForDomain(process, DomainClient.Factory.create(client), startupTimeout); + } else { + ServerHelper.waitForStandalone(process, client, startupTimeout); + } + if (!process.isAlive()) { + throw new MojoExecutionException("The process has been terminated before the start goal has completed."); + } + return new ServerContext() { + @Override + public Process process() { + return process; + } + + @Override + public CommandBuilder commandBuilder() { + return commandBuilder; + } + + @Override + public Path jbossHome() { + return jbossHome; + } + }; + } + } catch (MojoExecutionException e) { + throw e; + } catch (Exception e) { + throw new MojoExecutionException("The server failed to start", e); + } + } + + protected abstract CommandBuilder createCommandBuilder(final Path jbossHome) throws MojoExecutionException; + + protected StandardOutput standardOutput() throws IOException { + return StandardOutput.parse(null, false); + } + + /** + * Allows the {@link #javaOpts} to be set as a string. The string is assumed to be space delimited. + * + * @param value a spaced delimited value of JVM options + */ + @SuppressWarnings("unused") + public void setJavaOpts(final String value) { + if (value != null) { + javaOpts = value.split("\\s+"); + } + } + + protected StandaloneCommandBuilder createStandaloneCommandBuilder(final Path jbossHome, final String serverConfig) throws MojoExecutionException { + final StandaloneCommandBuilder commandBuilder = StandaloneCommandBuilder.of(jbossHome) + .setJavaHome(javaHome) + .addModuleDirs(modulesPath.getModulePaths()); + + // Set the JVM options + if (Utils.isNotNullOrEmpty(javaOpts)) { + commandBuilder.setJavaOptions(javaOpts); + } + if (debug) { + commandBuilder.addJavaOptions(String.format("-agentlib:jdwp=transport=dt_socket,server=y,suspend=%s,address=%s:%d", + debugSuspend ? "y" : "n", debugHost, debugPort)); + } + + if (serverConfig != null) { + commandBuilder.setServerConfiguration(serverConfig); + } + + if (propertiesFile != null) { + commandBuilder.setPropertiesFile(propertiesFile); + } + + if (serverArgs != null) { + commandBuilder.addServerArguments(serverArgs); + } + + final Path javaHomePath = (this.javaHome == null ? Paths.get(System.getProperty("java.home")) : Paths.get(this.javaHome)); + if (Environment.isModularJvm(javaHomePath)) { + commandBuilder.addJavaOptions(Environment.getModularJvmArguments()); + } + + // Print some server information + final Log log = getLog(); + log.info("JAVA_HOME : " + commandBuilder.getJavaHome()); + log.info("JBOSS_HOME: " + commandBuilder.getWildFlyHome()); + log.info("JAVA_OPTS : " + Utils.toString(commandBuilder.getJavaOptions(), " ")); + try { + addUsers(commandBuilder.getWildFlyHome(), commandBuilder.getJavaHome()); + } catch (IOException e) { + throw new MojoExecutionException("Failed to add users", e); + } + return commandBuilder; + } + + protected DomainCommandBuilder createDomainCommandBuilder(final Path jbossHome, final String domainConfig, final String hostConfig) throws MojoExecutionException { + final Path javaHome = (this.javaHome == null ? Paths.get(System.getProperty("java.home")) : Paths.get(this.javaHome)); + final DomainCommandBuilder commandBuilder = DomainCommandBuilder.of(jbossHome, javaHome) + .addModuleDirs(modulesPath.getModulePaths()); + + // Set the JVM options + if (Utils.isNotNullOrEmpty(javaOpts)) { + commandBuilder.setProcessControllerJavaOptions(javaOpts) + .setHostControllerJavaOptions(javaOpts); + } + + if (domainConfig != null) { + commandBuilder.setDomainConfiguration(domainConfig); + } + + if (hostConfig != null) { + commandBuilder.setHostConfiguration(hostConfig); + } + + if (propertiesFile != null) { + commandBuilder.setPropertiesFile(propertiesFile); + } + + if (serverArgs != null) { + commandBuilder.addServerArguments(serverArgs); + } + + // Workaround for WFCORE-4121 + if (Environment.isModularJvm(javaHome)) { + commandBuilder.addHostControllerJavaOptions(Environment.getModularJvmArguments()); + commandBuilder.addProcessControllerJavaOptions(Environment.getModularJvmArguments()); + } + + // Print some server information + final Log log = getLog(); + log.info("JAVA_HOME : " + commandBuilder.getJavaHome()); + log.info("JBOSS_HOME: " + commandBuilder.getWildFlyHome()); + log.info("JAVA_OPTS : " + Utils.toString(commandBuilder.getHostControllerJavaOptions(), " ")); + try { + addUsers(commandBuilder.getWildFlyHome(), commandBuilder.getJavaHome()); + } catch (IOException e) { + throw new MojoExecutionException("Failed to add users", e); + } + return commandBuilder; + } + + private Path provisionIfRequired(final Path installDir) throws MojoFailureException { + if (jbossHome != null) { + //we do not need to download WildFly + return Paths.get(jbossHome); + } + try { + if (!Files.exists(installDir)) { + getLog().info("Provisioning default server in " + installDir); + GalleonUtils.provision(installDir, resolveFeaturePackLocation(), version, mavenRepoManager); + } + return installDir; + } catch (ProvisioningException ex) { + throw new MojoFailureException(ex.getLocalizedMessage(), ex); + } + } + + private void addUsers(final Path wildflyHome, final Path javaHome) throws IOException { + if (addUser != null && addUser.hasUsers()) { + getLog().info("Adding users: " + addUser); + addUser.addUsers(wildflyHome, javaHome); + } + } + + private String resolveFeaturePackLocation() { + return featurePackLocation == null ? getDefaultFeaturePackLocation() : featurePackLocation; + } + + /** + * Returns the default feature pack location if not defined in the configuration. + * + * @return the default feature pack location + */ + protected String getDefaultFeaturePackLocation() { + return "wildfly@maven(org.jboss.universe:community-universe)"; + } +} diff --git a/plugin/src/main/java/org/wildfly/plugin/server/RunMojo.java b/plugin/src/main/java/org/wildfly/plugin/server/RunMojo.java index c557473f..0d0f11eb 100644 --- a/plugin/src/main/java/org/wildfly/plugin/server/RunMojo.java +++ b/plugin/src/main/java/org/wildfly/plugin/server/RunMojo.java @@ -23,48 +23,28 @@ package org.wildfly.plugin.server; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import javax.inject.Inject; -import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Execute; 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.MavenProject; -import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.repository.RemoteRepository; import org.jboss.as.controller.client.ModelControllerClient; -import org.jboss.galleon.ProvisioningException; -import org.jboss.galleon.maven.plugin.util.MavenArtifactRepositoryManager; -import org.jboss.galleon.universe.maven.repo.MavenRepoManager; import org.wildfly.core.launcher.CommandBuilder; -import org.wildfly.core.launcher.Launcher; -import org.wildfly.core.launcher.StandaloneCommandBuilder; import org.wildfly.plugin.cli.CommandConfiguration; import org.wildfly.plugin.cli.CommandExecutor; -import org.wildfly.plugin.common.AbstractServerConnection; import org.wildfly.plugin.common.PropertyNames; -import org.wildfly.plugin.common.Utils; import org.wildfly.plugin.core.Deployment; import org.wildfly.plugin.core.DeploymentManager; -import org.wildfly.plugin.core.GalleonUtils; -import org.wildfly.plugin.core.MavenRepositoriesEnricher; -import org.wildfly.plugin.core.ServerHelper; import org.wildfly.plugin.deployment.PackageType; /** @@ -77,79 +57,11 @@ */ @Mojo(name = "run", requiresDependencyResolution = ResolutionScope.RUNTIME) @Execute(phase = LifecyclePhase.PACKAGE) -public class RunMojo extends AbstractServerConnection { - - @Component - RepositorySystem repoSystem; - - @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true) - private RepositorySystemSession session; - - @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) - private List repositories; - - @Parameter(defaultValue = "${project}", readonly = true, required = true) - MavenProject project; - - @Parameter(defaultValue = "${session}", readonly = true, required = true) - MavenSession mavenSession; +public class RunMojo extends AbstractServerStartMojo { @Inject private CommandExecutor commandExecutor; - /** - * The WildFly Application Server's home directory. If not used, WildFly will be installed. - */ - @Parameter(alias = "jboss-home", property = PropertyNames.JBOSS_HOME) - private String jbossHome; - - /** - * The feature pack location. See the documentation - * for details on how to format a feature pack location. - *

- * Note that if you define the version in the feature pack location, e.g. {@code #26.1.1.Final}, the {@code version} - * configuration parameter should be left blank. - *

- */ - @Parameter(alias = "feature-pack-location", property = PropertyNames.WILDFLY_FEATURE_PACK_LOCATION) - private String featurePackLocation; - - /** - * The version of the WildFly default server to install in case no jboss-home has been set - * and no server has previously been provisioned. - * The latest stable version is resolved if left blank. - */ - @Parameter(property = PropertyNames.WILDFLY_VERSION) - private String version; - - /** - * The directory name inside the buildDir where to provision the default server. - * By default the server is provisioned into the 'server' directory. - * - * @since 3.0 - */ - @Parameter(alias = "provisioning-dir", property = PropertyNames.WILDFLY_PROVISIONING_DIR, defaultValue = Utils.WILDFLY_DEFAULT_DIR) - private String provisioningDir; - - /** - * The modules path or paths to use. A single path can be used or multiple paths by enclosing them in a paths - * element. - */ - @Parameter(alias = "modules-path", property = PropertyNames.MODULES_PATH) - private ModulesPath modulesPath; - - /** - * The JVM options to use. - */ - @Parameter(alias = "java-opts", property = PropertyNames.JAVA_OPTS) - private String[] javaOpts; - - /** - * The {@code JAVA_HOME} to use for launching the server. - */ - @Parameter(alias = "java-home", property = PropertyNames.JAVA_HOME) - private String javaHome; - /** * The CLI commands to execute before the deployment is deployed. */ @@ -168,43 +80,6 @@ public class RunMojo extends AbstractServerConnection { @Parameter(alias = "server-config", property = PropertyNames.SERVER_CONFIG) private String serverConfig; - /** - * The path to the system properties file to load. - */ - @Parameter(alias = "properties-file", property = PropertyNames.PROPERTIES_FILE) - private String propertiesFile; - - /** - * The timeout value to use when starting the server. - */ - @Parameter(alias = "startup-timeout", defaultValue = "60", property = PropertyNames.STARTUP_TIMEOUT) - private long startupTimeout; - - /** - * The arguments to be passed to the server. - */ - @Parameter(alias = "server-args", property = PropertyNames.SERVER_ARGS) - private String[] serverArgs; - - /** - * The users to add to the server. - */ - @Parameter(alias = "add-user", property = "wildfly.add-user") - private AddUser addUser; - - /** - * Specifies the environment variables to be passed to the process being started. - *
- *
-     * <env>
-     *     <HOME>/home/wildfly/</HOME>
-     * </env>
-     * 
- *
- */ - @Parameter - private Map env; - /** * Specifies the name used for the deployment. */ @@ -240,52 +115,28 @@ public class RunMojo extends AbstractServerConnection { @Parameter(property = PropertyNames.DEPLOYMENT_FILENAME) private String filename; - /** - * Set to {@code true} if you want the deployment to be skipped, otherwise {@code false}. - */ - @Parameter(defaultValue = "false", property = PropertyNames.SKIP) - private boolean skip; - - private MavenRepoManager mavenRepoManager; - @Override public void execute() throws MojoExecutionException, MojoFailureException { if (skip) { return; } - MavenRepositoriesEnricher.enrich(mavenSession, project, repositories); - mavenRepoManager = new MavenArtifactRepositoryManager(repoSystem, session, repositories); - final Log log = getLog(); - final Path deploymentContent = getDeploymentContent(); - // The deployment must exist before we do anything - if (Files.notExists(deploymentContent)) { - throw new MojoExecutionException(String.format("The deployment '%s' could not be found.", deploymentContent.toAbsolutePath())); - } - // Validate the environment - final Path wildflyPath = provisionIfRequired(deploymentContent.getParent().resolve(provisioningDir)); - if (!ServerHelper.isValidHomeDirectory(wildflyPath)) { - throw new MojoExecutionException(String.format("JBOSS_HOME '%s' is not a valid directory.", wildflyPath)); - } - final StandaloneCommandBuilder commandBuilder = createCommandBuilder(wildflyPath); - - // Print some server information - log.info("JAVA_HOME : " + commandBuilder.getJavaHome()); - log.info("JBOSS_HOME: " + commandBuilder.getWildFlyHome()); - log.info("JAVA_OPTS : " + Utils.toString(commandBuilder.getJavaOptions(), " ")); try { - if (addUser != null && addUser.hasUsers()) { - log.info("Adding users: " + addUser); - addUser.addUsers(commandBuilder.getWildFlyHome(), commandBuilder.getJavaHome()); + final Log log = getLog(); + final Path deploymentContent = getDeploymentContent(); + // The deployment must exist before we do anything + if (Files.notExists(deploymentContent)) { + throw new MojoExecutionException(String.format("The deployment '%s' could not be found.", deploymentContent.toAbsolutePath())); } // Start the server log.info("Server is starting up. Press CTRL + C to stop the server."); - Process process = startContainer(commandBuilder); + final ServerContext context = startServer(ServerType.STANDALONE); + Process process = context.process(); try (ModelControllerClient client = createClient()) { // Execute commands before the deployment is done final CommandConfiguration cmdConfig = CommandConfiguration.of(this::createClient, this::getClientConfiguration) .addCommands(commands) .addScripts(scripts) - .setJBossHome(commandBuilder.getWildFlyHome()) + .setJBossHome(context.jbossHome()) .setFork(true) .setStdout("none") .setTimeout(timeout) @@ -312,7 +163,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (exitCode == 10) { // Ensure the current process is destroyed and restart a new one process.destroy(); - process = startContainer(commandBuilder); + process = startServer(ServerType.STANDALONE).process(); } else { keepRunning = false; } @@ -327,56 +178,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } - /** - * Allows the {@link #javaOpts} to be set as a string. The string is assumed to be space delimited. - * - * @param value a spaced delimited value of JVM options - */ - @SuppressWarnings("unused") - public void setJavaOpts(final String value) { - if (value != null) { - javaOpts = value.split("\\s+"); - } - } - - private StandaloneCommandBuilder createCommandBuilder(final Path wildflyPath) { - final StandaloneCommandBuilder commandBuilder = StandaloneCommandBuilder.of(wildflyPath) - .setJavaHome(javaHome) - .addModuleDirs(modulesPath.getModulePaths()); - - // Set the JVM options - if (Utils.isNotNullOrEmpty(javaOpts)) { - commandBuilder.setJavaOptions(javaOpts); - } - - if (serverConfig != null) { - commandBuilder.setServerConfiguration(serverConfig); - } - - if (propertiesFile != null) { - commandBuilder.setPropertiesFile(propertiesFile); - } - - if (serverArgs != null) { - commandBuilder.addServerArguments(serverArgs); - } - return commandBuilder; - } - - private Path provisionIfRequired(final Path installDir) throws MojoFailureException { - if (jbossHome != null) { - //we do not need to download WildFly - return Paths.get(jbossHome); - } - try { - if (!Files.exists(installDir)) { - getLog().info("Provisioning default server in " + installDir); - GalleonUtils.provision(installDir, resolveFeaturePackLocation(), version, mavenRepoManager); - } - return installDir; - } catch (ProvisioningException ex) { - throw new MojoFailureException(ex.getLocalizedMessage(), ex); - } + @Override + protected CommandBuilder createCommandBuilder(final Path jbossHome) throws MojoExecutionException { + return createStandaloneCommandBuilder(jbossHome, serverConfig); } @Override @@ -384,32 +188,6 @@ public String goal() { return "run"; } - private String resolveFeaturePackLocation() { - return featurePackLocation == null ? getDefaultFeaturePackLocation() : featurePackLocation; - } - - /** - * Returns the default feature pack location if not defined in the configuration. - * - * @return the default feature pack location - */ - protected String getDefaultFeaturePackLocation() { - return "wildfly@maven(org.jboss.universe:community-universe)"; - } - - private Process startContainer(final CommandBuilder commandBuilder) throws IOException, InterruptedException, TimeoutException { - final Launcher launcher = Launcher.of(commandBuilder) - .inherit(); - if (env != null) { - launcher.addEnvironmentVariables(env); - } - final Process process = launcher.launch(); - try (ModelControllerClient client = createClient()) { - ServerHelper.waitForStandalone(process, client, startupTimeout); - } - return process; - } - private Path getDeploymentContent() { final PackageType packageType = PackageType.resolve(project); final String filename; diff --git a/plugin/src/main/java/org/wildfly/plugin/server/ServerContext.java b/plugin/src/main/java/org/wildfly/plugin/server/ServerContext.java new file mode 100644 index 00000000..8fcb92e3 --- /dev/null +++ b/plugin/src/main/java/org/wildfly/plugin/server/ServerContext.java @@ -0,0 +1,53 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual 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.plugin.server; + +import java.nio.file.Path; + +import org.wildfly.core.launcher.CommandBuilder; + +/** + * The context of a server that has been started. + * + * @author James R. Perkins + */ +public interface ServerContext { + + /** + * The running process. + * + * @return the process + */ + Process process(); + + /** + * The command builder used to start the server. + * + * @return the command builder + */ + CommandBuilder commandBuilder(); + + /** + * The directory used to start the server. + * + * @return the server directory + */ + Path jbossHome(); +} diff --git a/plugin/src/main/java/org/wildfly/plugin/server/StartMojo.java b/plugin/src/main/java/org/wildfly/plugin/server/StartMojo.java index acc03d16..977a4355 100644 --- a/plugin/src/main/java/org/wildfly/plugin/server/StartMojo.java +++ b/plugin/src/main/java/org/wildfly/plugin/server/StartMojo.java @@ -22,43 +22,18 @@ package org.wildfly.plugin.server; -import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.annotations.Component; 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.MavenProject; -import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.repository.RemoteRepository; -import org.jboss.as.controller.client.ModelControllerClient; -import org.jboss.as.controller.client.helpers.domain.DomainClient; -import org.jboss.galleon.ProvisioningException; -import org.jboss.galleon.maven.plugin.util.MavenArtifactRepositoryManager; -import org.jboss.galleon.universe.maven.repo.MavenRepoManager; import org.wildfly.core.launcher.CommandBuilder; -import org.wildfly.core.launcher.DomainCommandBuilder; -import org.wildfly.core.launcher.Launcher; -import org.wildfly.core.launcher.StandaloneCommandBuilder; -import org.wildfly.plugin.common.AbstractServerConnection; -import org.wildfly.plugin.common.Environment; import org.wildfly.plugin.common.PropertyNames; import org.wildfly.plugin.common.StandardOutput; -import org.wildfly.plugin.common.Utils; -import org.wildfly.plugin.core.GalleonUtils; -import org.wildfly.plugin.core.MavenRepositoriesEnricher; -import org.wildfly.plugin.core.ServerHelper; /** * Starts a standalone instance of WildFly Application Server. @@ -68,80 +43,7 @@ * @author James R. Perkins */ @Mojo(name = "start", requiresDependencyResolution = ResolutionScope.RUNTIME) -public class StartMojo extends AbstractServerConnection { - - @Component - RepositorySystem repoSystem; - - @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true) - private RepositorySystemSession session; - - @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) - private List repositories; - - @Parameter(defaultValue = "${project}", readonly = true, required = true) - MavenProject project; - - @Parameter(defaultValue = "${session}", readonly = true, required = true) - MavenSession mavenSession; - /** - * The target directory the application to be deployed is located. - */ - @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true) - private File targetDir; - - /** - * The WildFly Application Server's home directory. If not used, WildFly will be downloaded. - */ - @Parameter(alias = "jboss-home", property = PropertyNames.JBOSS_HOME) - private String jbossHome; - - /** - * The feature pack location. See the documentation - * for details on how to format a feature pack location. - *

- * Note that if you define the version in the feature pack location, e.g. {@code #26.1.1.Final}, the {@code version} - * configuration parameter should be left blank. - *

- */ - @Parameter(alias = "feature-pack-location", property = PropertyNames.WILDFLY_FEATURE_PACK_LOCATION) - private String featurePackLocation; - - /** - * The version of the WildFly default server to install in case no jboss-home has been set - * and no server has previously been provisioned. - * The latest stable version is resolved if left blank. - */ - @Parameter(alias = "version", property = PropertyNames.WILDFLY_VERSION) - private String version; - - /** - * The directory name inside the buildDir where to provision the default server. - * By default the server is provisioned into the 'server' directory. - * - * @since 3.0 - */ - @Parameter(alias = "provisioning-dir", property = PropertyNames.WILDFLY_PROVISIONING_DIR, defaultValue = Utils.WILDFLY_DEFAULT_DIR) - private String provisioningDir; - - /** - * The modules path or paths to use. A single path can be used or multiple paths by enclosing them in a paths - * element. - */ - @Parameter(alias = "modules-path", property = PropertyNames.MODULES_PATH) - private ModulesPath modulesPath; - - /** - * The JVM options to use. - */ - @Parameter(alias = "java-opts", property = PropertyNames.JAVA_OPTS) - private String[] javaOpts; - - /** - * The {@code JAVA_HOME} to use for launching the server. - */ - @Parameter(alias = "java-home", property = PropertyNames.JAVA_HOME) - private String javaHome; +public class StartMojo extends AbstractServerStartMojo { /** * The path to the server configuration to use. This is only used for standalone servers. @@ -162,28 +64,13 @@ public class StartMojo extends AbstractServerConnection { private String hostConfig; /** - * The path to the system properties file to load. - */ - @Parameter(alias = "properties-file", property = PropertyNames.PROPERTIES_FILE) - private String propertiesFile; - - /** - * The timeout value to use when starting the server. - */ - @Parameter(alias = "startup-timeout", defaultValue = "60", property = PropertyNames.STARTUP_TIMEOUT) - private long startupTimeout; - - /** - * The arguments to be passed to the server. - */ - @Parameter(alias = "server-args", property = PropertyNames.SERVER_ARGS) - private String[] serverArgs; - - /** - * Set to {@code true} if you want to skip server start, otherwise {@code false}. + * The type of server to start. + *

+ * {@code STANDALONE} for a standalone server and {@code DOMAIN} for a domain server. + *

*/ - @Parameter(defaultValue = "false", property = PropertyNames.SKIP) - private boolean skip; + @Parameter(alias = "server-type", property = "wildfly.server.type", defaultValue = "STANDALONE") + protected ServerType serverType; /** * Indicates how {@code stdout} and {@code stderr} should be handled for the spawned server process. Note that @@ -209,36 +96,6 @@ public class StartMojo extends AbstractServerConnection { @Parameter(property = PropertyNames.STDOUT) private String stdout; - /** - * The users to add to the server. - */ - @Parameter(alias = "add-user", property = "wildfly.add-user") - private AddUser addUser; - - /** - * The type of server to start. - *

- * {@code STANDALONE} for a standalone server and {@code DOMAIN} for a domain server. - *

- */ - @Parameter(alias = "server-type", property = "wildfly.server.type", defaultValue = "STANDALONE") - private ServerType serverType; - - private MavenRepoManager mavenRepoManager; - - /** - * Specifies the environment variables to be passed to the process being started. - *
- *
-     * <env>
-     *     <HOME>/home/wildfly/</HOME>
-     * </env>
-     * 
- *
- */ - @Parameter - private Map env; - @Override public void execute() throws MojoExecutionException, MojoFailureException { final Log log = getLog(); @@ -246,46 +103,10 @@ public void execute() throws MojoExecutionException, MojoFailureException { log.debug("Skipping server start"); return; } - MavenRepositoriesEnricher.enrich(mavenSession, project, repositories); - mavenRepoManager = new MavenArtifactRepositoryManager(repoSystem, session, repositories); - - // Validate the environment - final Path jbossHome = provisionIfRequired(targetDir.toPath().resolve(provisioningDir)); - if (!ServerHelper.isValidHomeDirectory(jbossHome)) { - throw new MojoExecutionException(String.format("JBOSS_HOME '%s' is not a valid directory.", jbossHome)); - } // Determine how stdout should be consumed try { - final StandardOutput out = StandardOutput.parse(stdout, true); - // Create the server and close the client after the start. The process will continue running even after - // the maven process may have been finished - try (ModelControllerClient client = createClient()) { - if (ServerHelper.isStandaloneRunning(client) || ServerHelper.isDomainRunning(client)) { - throw new MojoExecutionException(String.format("%s server is already running?", serverType)); - } - final CommandBuilder commandBuilder = createCommandBuilder(jbossHome); - log.info(String.format("%s server is starting up.", serverType)); - final Launcher launcher = Launcher.of(commandBuilder) - .setRedirectErrorStream(true); - if (env != null) { - launcher.addEnvironmentVariables(env); - } - out.getRedirect().ifPresent(launcher::redirectOutput); - - final Process process = launcher.launch(); - // Note that if this thread is started and no shutdown goal is executed this stop the stdout and stderr - // from being logged any longer. The user was warned in the documentation. - out.startConsumer(process); - if (serverType == ServerType.DOMAIN) { - ServerHelper.waitForDomain(process, DomainClient.Factory.create(client), startupTimeout); - } else { - ServerHelper.waitForStandalone(process, client, startupTimeout); - } - if (!process.isAlive()) { - throw new MojoExecutionException("The process has been terminated before the start goal has completed."); - } - } + startServer(serverType); } catch (MojoExecutionException e) { throw e; } catch (Exception e) { @@ -293,145 +114,17 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } - /** - * Allows the {@link #javaOpts} to be set as a string. The string is assumed to be space delimited. - * - * @param value a spaced delimited value of JVM options - */ - @SuppressWarnings("unused") - public void setJavaOpts(final String value) { - if (value != null) { - javaOpts = value.split("\\s+"); - } - } - - private CommandBuilder createCommandBuilder(final Path jbossHome) throws MojoExecutionException { + @Override + protected CommandBuilder createCommandBuilder(final Path jbossHome) throws MojoExecutionException { if (serverType == ServerType.DOMAIN) { - return createDomainCommandBuilder(jbossHome); - } - return createStandaloneCommandBuilder(jbossHome); - } - - private CommandBuilder createStandaloneCommandBuilder(final Path jbossHome) throws MojoExecutionException { - final StandaloneCommandBuilder commandBuilder = StandaloneCommandBuilder.of(jbossHome) - .setJavaHome(javaHome) - .addModuleDirs(modulesPath.getModulePaths()); - - // Set the JVM options - if (Utils.isNotNullOrEmpty(javaOpts)) { - commandBuilder.setJavaOptions(javaOpts); - } - - if (serverConfig != null) { - commandBuilder.setServerConfiguration(serverConfig); - } - - if (propertiesFile != null) { - commandBuilder.setPropertiesFile(propertiesFile); - } - - if (serverArgs != null) { - commandBuilder.addServerArguments(serverArgs); - } - - final Path javaHomePath = (this.javaHome == null ? Paths.get(System.getProperty("java.home")) : Paths.get(this.javaHome)); - if (Environment.isModularJvm(javaHomePath)) { - commandBuilder.addJavaOptions(Environment.getModularJvmArguments()); - } - - // Print some server information - final Log log = getLog(); - log.info("JAVA_HOME : " + commandBuilder.getJavaHome()); - log.info("JBOSS_HOME: " + commandBuilder.getWildFlyHome()); - log.info("JAVA_OPTS : " + Utils.toString(commandBuilder.getJavaOptions(), " ")); - try { - addUsers(commandBuilder.getWildFlyHome(), commandBuilder.getJavaHome()); - } catch (IOException e) { - throw new MojoExecutionException("Failed to add users", e); - } - return commandBuilder; - } - - private CommandBuilder createDomainCommandBuilder(final Path jbossHome) throws MojoExecutionException { - final Path javaHome = (this.javaHome == null ? Paths.get(System.getProperty("java.home")) : Paths.get(this.javaHome)); - final DomainCommandBuilder commandBuilder = DomainCommandBuilder.of(jbossHome, javaHome) - .addModuleDirs(modulesPath.getModulePaths()); - - // Set the JVM options - if (Utils.isNotNullOrEmpty(javaOpts)) { - commandBuilder.setProcessControllerJavaOptions(javaOpts) - .setHostControllerJavaOptions(javaOpts); - } - - if (domainConfig != null) { - commandBuilder.setDomainConfiguration(domainConfig); - } - - if (hostConfig != null) { - commandBuilder.setHostConfiguration(hostConfig); - } - - if (propertiesFile != null) { - commandBuilder.setPropertiesFile(propertiesFile); + return createDomainCommandBuilder(jbossHome, domainConfig, hostConfig); } - - if (serverArgs != null) { - commandBuilder.addServerArguments(serverArgs); - } - - // Workaround for WFCORE-4121 - if (Environment.isModularJvm(javaHome)) { - commandBuilder.addHostControllerJavaOptions(Environment.getModularJvmArguments()); - commandBuilder.addProcessControllerJavaOptions(Environment.getModularJvmArguments()); - } - - // Print some server information - final Log log = getLog(); - log.info("JAVA_HOME : " + commandBuilder.getJavaHome()); - log.info("JBOSS_HOME: " + commandBuilder.getWildFlyHome()); - log.info("JAVA_OPTS : " + Utils.toString(commandBuilder.getHostControllerJavaOptions(), " ")); - try { - addUsers(commandBuilder.getWildFlyHome(), commandBuilder.getJavaHome()); - } catch (IOException e) { - throw new MojoExecutionException("Failed to add users", e); - } - return commandBuilder; - } - - private Path provisionIfRequired(final Path installDir) throws MojoFailureException { - if (jbossHome != null) { - //we do not need to download WildFly - return Paths.get(jbossHome); - } - try { - if (!Files.exists(installDir)) { - getLog().info("Provisioning default server in " + installDir); - GalleonUtils.provision(installDir, resolveFeaturePackLocation(), version, mavenRepoManager); - } - return installDir; - } catch (ProvisioningException ex) { - throw new MojoFailureException(ex.getLocalizedMessage(), ex); - } - } - - private void addUsers(final Path wildflyHome, final Path javaHome) throws IOException { - if (addUser != null && addUser.hasUsers()) { - getLog().info("Adding users: " + addUser); - addUser.addUsers(wildflyHome, javaHome); - } - } - - private String resolveFeaturePackLocation() { - return featurePackLocation == null ? getDefaultFeaturePackLocation() : featurePackLocation; + return createStandaloneCommandBuilder(jbossHome, serverConfig); } - /** - * Returns the default feature pack location if not defined in the configuration. - * - * @return the default feature pack location - */ - protected String getDefaultFeaturePackLocation() { - return "wildfly@maven(org.jboss.universe:community-universe)"; + @Override + protected StandardOutput standardOutput() throws IOException { + return StandardOutput.parse(stdout, true); } @Override diff --git a/plugin/src/site/markdown/dev-example.md.vm b/plugin/src/site/markdown/dev-example.md.vm new file mode 100644 index 00000000..33808fcd --- /dev/null +++ b/plugin/src/site/markdown/dev-example.md.vm @@ -0,0 +1,141 @@ +# Dev Examples + +The dev goal allows you to run a local instance of ${appServerName} and watches the source directories for changes. If +required your deployment will be recompiled and possibly redeployed. This allows for more rapid development. Do note +that large deployments may take longer to deploy. + +#[[##]]# Run overriding the feature pack location + +The example below shows how to run a server overriding the feature pack location: + +```xml + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + wildfly-preview@maven(org.jboss.universe:community-universe) + + + ... + + ... + +... + +``` + +#[[##]]# Run ignoring redeployment if properties files are changed + +The example below shows how to ignore properties files from triggering a redeploy: + +```xml + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + .properties + + + + ... + + ... + +... + +``` + +#[[##]]# Add a user before running the server + +The example below shows how to add a user before running the server + +```xml + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + + + admin + admin.1234 + + + admin-user + user.1234 + + admin + user + + true + + + default-user + user.1234 + + user + + true + + + + + + ... + + ... + +... + +``` + +#[[##]]# Enable debugging + +The example below shows how to run a server in dev mode with debugging enabled + +```xml + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + true + 5005 + true + + + ... + + ... + +... + +``` \ No newline at end of file diff --git a/plugin/src/site/markdown/index.md.vm b/plugin/src/site/markdown/index.md.vm index a40610a4..58cddb83 100644 --- a/plugin/src/site/markdown/index.md.vm +++ b/plugin/src/site/markdown/index.md.vm @@ -71,6 +71,8 @@ Of course, patches are welcome, too. Contributors can check out the project from * [Execute Commands Example](./execute-commands-example.html) + * [Dev Example](./dev-example.html) + * [Run Example](./run-example.html) * [Packaging the application and server Example](./package-example.html) diff --git a/plugin/src/site/markdown/run-example.md.vm b/plugin/src/site/markdown/run-example.md.vm index d75cea73..909c8959 100644 --- a/plugin/src/site/markdown/run-example.md.vm +++ b/plugin/src/site/markdown/run-example.md.vm @@ -187,9 +187,9 @@ The example below shows how to run a server with debugging enabled ${project.artifactId} ${project.version} - - -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 - + true + 5005 + true ... diff --git a/plugin/src/site/markdown/usage.md.vm b/plugin/src/site/markdown/usage.md.vm index 2d03e6a8..fa180061 100644 --- a/plugin/src/site/markdown/usage.md.vm +++ b/plugin/src/site/markdown/usage.md.vm @@ -10,7 +10,7 @@ The `${pluginPrefix}:add-resource` goal adds a resource to the running ${appServ For example to add a resource you type the following on the command line: -```bash +``` mvn ${pluginPrefix}:add-resource ``` @@ -20,7 +20,7 @@ The `${pluginPrefix}:deploy` goal deploys the application to the running ${appSe For example to deploy, or redeploy by default, you type the following on the command line: -```bash +``` mvn ${pluginPrefix}:deploy ``` @@ -31,7 +31,7 @@ any other goals by default. For example to deploy, or redeploy by default, you type the following on the command line: -```bash +``` mvn ${pluginPrefix}:deploy-only ``` @@ -41,17 +41,29 @@ The `${pluginPrefix}:deploy-artifact` goal deploys an arbitrary artifact to the For example to deploy the arbitrary artifact specified in you POM, you type the following on the command line: -```bash +``` mvn ${pluginPrefix}:deploy-artifact ``` +#[[##]]# The `${pluginPrefix}:dev` Goal + +The `${pluginPrefix}:dev` goal will start ${appServerName} and deploy your application. If the `jboss-home` +property is not set, a server will be provisioned. The source directories will be watched for changes. If a change +occurs, additional Maven goals may be executed before a possible redeploy of your deployment. + +For example to run your WAR in development mode, you enter the following on the command line: + +``` +mvn ${pluginPrefix}:dev +``` + #[[##]]# The `${pluginPrefix}:package` Goal The `${pluginPrefix}:package` goal will provision a server, execute CLI commands and deploy your application. To execute the package goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:package ``` @@ -61,7 +73,7 @@ The `${pluginPrefix}:provision` goal will provision a server. To execute the provision goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:provision ``` @@ -72,7 +84,7 @@ deployed to the application server. To execute the redeploy goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:redeploy ``` @@ -83,7 +95,7 @@ deployed to the application server. By default no other goals are invoked. To execute the redeploy goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:redeploy-only ``` @@ -94,30 +106,30 @@ application has already been deployed. To execute the undeploy goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:undeploy ``` #[[##]]# The `${pluginPrefix}:run` Goal -The `${pluginPrefix}:run>>> goal will run ${appServerName} and deploy your application. If the <<<${pluginPrefix}.home` -property is not set, the server will be downloaded. +The `${pluginPrefix}:run` goal will run ${appServerName} and deploy your application. If the `jboss-home` +property is not set, a server will be provisioned. To execute the run goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:run ``` #[[##]]# The `${pluginPrefix}:start` Goal -The `${pluginPrefix}:start` goal will start a ${appServerName}. If the `${pluginPrefix}.home` property is not set, -the server will be downloaded. The server will continue to run until the shutdown goal is executed, a shutdown management +The `${pluginPrefix}:start` goal will start a ${appServerName}. If the `jboss-home` property is not set, +a server will be provisioned. The server will continue to run until the shutdown goal is executed, a shutdown management operation has been issued or the process is killed. To execute the start goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:start ``` @@ -127,7 +139,7 @@ The `${pluginPrefix}:shutdown` goal will shutdown a running ${appServerName}. To execute the shutdown goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:shutdown ``` @@ -137,6 +149,6 @@ The `${pluginPrefix}:execute-commands` goal will execute commands, formatted lik To execute the execute-commands goal type the following on the command line: -```bash +``` mvn ${pluginPrefix}:execute-commands ``` \ No newline at end of file diff --git a/plugin/src/site/site.xml b/plugin/src/site/site.xml index 868093eb..7cbedf17 100644 --- a/plugin/src/site/site.xml +++ b/plugin/src/site/site.xml @@ -59,6 +59,7 @@ + diff --git a/pom.xml b/pom.xml index 58f011fa..224d79ca 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ 0.9.1 1.1.0 0.3.5 + 2.3.1 2.3.1