
io.openliberty.tools.maven.server.DevMojo Maven / Gradle / Ivy
/**
* (C) Copyright IBM Corporation 2019, 2023.
*
* 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 io.openliberty.tools.maven.server;
import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
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.name;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.Resource;
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.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingResult;
import org.apache.maven.rtinfo.RuntimeInformation;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.twdata.maven.mojoexecutor.MojoExecutor.Element;
import io.openliberty.tools.ant.ServerTask;
import io.openliberty.tools.common.plugins.util.BinaryScannerUtil;
import io.openliberty.tools.common.plugins.util.DevUtil;
import io.openliberty.tools.common.plugins.util.InstallFeatureUtil;
import io.openliberty.tools.common.plugins.util.JavaCompilerOptions;
import io.openliberty.tools.common.plugins.util.PluginExecutionException;
import io.openliberty.tools.common.plugins.util.PluginScenarioException;
import io.openliberty.tools.common.plugins.util.ProjectModule;
import io.openliberty.tools.common.plugins.util.ServerFeatureUtil;
import io.openliberty.tools.common.plugins.util.ServerStatusUtil;
import io.openliberty.tools.maven.BasicSupport;
import io.openliberty.tools.maven.applications.DeployMojoSupport;
import io.openliberty.tools.maven.applications.LooseWarApplication;
import io.openliberty.tools.maven.utils.DevHelper;
import io.openliberty.tools.maven.utils.ExecuteMojoUtil;
/**
* Start a liberty server in dev mode import to set ResolutionScope for TEST as
* it helps build full transitive dependency classpath
*/
@Mojo(name = "dev", requiresDependencyCollection = ResolutionScope.TEST, requiresDependencyResolution = ResolutionScope.TEST)
public class DevMojo extends LooseAppSupport {
private static final String TEST_RUN_ID_PROPERTY_NAME = "liberty.dev.test.run.id";
private static final String LIBERTY_HOSTNAME = "liberty.hostname";
private static final String LIBERTY_HTTP_PORT = "liberty.http.port";
private static final String LIBERTY_HTTPS_PORT = "liberty.https.port";
private static final String MICROSHED_HOSTNAME = "microshed_hostname";
private static final String MICROSHED_HTTP_PORT = "microshed_http_port";
private static final String MICROSHED_HTTPS_PORT = "microshed_https_port";
private static final String WLP_USER_DIR_PROPERTY_NAME = "wlp.user.dir";
private static final String GEN_FEAT_LIBERTY_DEP_WARNING = "Liberty ESA feature dependencies were detected in the pom.xml file and automatic generation of features is [On]. "
+ "Automatic generation of features does not support Liberty ESA feature dependencies. "
+ "Remove any Liberty ESA feature dependencies from the pom.xml file or disable automatic generation of features by typing 'g' and press Enter.";
DevMojoUtil util = null;
@Parameter(property = "hotTests", defaultValue = "false")
private boolean hotTests;
@Parameter(property = "skipTests", defaultValue = "false")
private boolean skipTests;
@Parameter(property = "skipUTs", defaultValue = "false")
private boolean skipUTs;
@Parameter(property = "skipITs", defaultValue = "false")
private boolean skipITs;
/**
* If set to `true`, the `install-feature` goal will be skipped when `dev` mode is started on an already existing Liberty runtime installation.
* It will also be skipped when `dev` mode is running and a restart of the server is triggered either directly by the user or by application changes.
* The `install-feature` goal will be invoked though when `dev` mode is running and a change to the configured features is detected.
* The default value is `false`.
*/
@Parameter(property = "skipInstallFeature", defaultValue = "false")
protected boolean skipInstallFeature;
@Parameter(property = "debug", defaultValue = "true")
private boolean libertyDebug;
@Parameter(property = "debugPort", defaultValue = "7777")
private int libertyDebugPort;
@Parameter(property = "container", defaultValue = "false")
private boolean container;
@Parameter(property = "generateFeatures", defaultValue = "false")
private boolean generateFeatures;
/**
* Whether to recompile dependencies. Defaults to false for single module
* projects and true for multi module projects. Since the default behavior
* changes between single module and multi module projects, need to accept param
* as a string.
*/
@Parameter(property = "recompileDependencies")
private String recompileDependencies;
/**
* Time in seconds to wait before processing Java changes and deletions.
*/
@Parameter(property = "compileWait", defaultValue = "0.5")
private double compileWait;
private int runId = 0;
private ServerTask serverTask = null;
private Plugin boostPlugin = null;
@Component
protected ProjectBuilder mavenProjectBuilder;
@Component
private RuntimeInformation runtime;
/**
* Time in seconds to wait while verifying that the application has started or
* updated.
*/
@Parameter(property = "verifyTimeout", defaultValue = "30")
private int verifyTimeout;
/**
* Time in seconds to wait while verifying that the server has started.
*/
@Parameter(property = "serverStartTimeout", defaultValue = "90")
private int serverStartTimeout;
/**
* comma separated list of app names to wait for
*/
@Parameter(property = "applications")
private String applications;
/**
* Clean all cached information on server start up.
*/
@Parameter(property = "clean", defaultValue = "false")
protected boolean clean;
/**
* Poll for file changes instead of using file system notifications (test only).
*/
@Parameter(property = "pollingTest", defaultValue = "false")
protected boolean pollingTest;
/**
* Containerfile used to build an image to then start a container with
*/
@Parameter(alias="containerfile", property = "containerfile")
private File containerfile;
@Parameter(alias="dockerfile", property = "dockerfile")
public void setDockerfile(File dockerfile) {
if (dockerfile != null && this.containerfile != null) {
getLog().warn("Both containerfile and dockerfile have been set. Using containerfile value.");
} else {
this.containerfile = dockerfile;
}
}
/**
* Context (directory) to use for the build when building the container image
*/
@Parameter(alias="containerBuildContext", property = "containerBuildContext")
private File containerBuildContext;
@Parameter(alias="dockerBuildContext", property = "dockerBuildContext")
public void setDockerBuildContext(File dockerBuildContext) {
if (dockerBuildContext != null && this.containerBuildContext != null) {
getLog().warn("Both containerBuildContext and dockerBuildContext have been set. Using containerBuildContext value.");
} else {
this.containerBuildContext = dockerBuildContext;
}
}
/**
* The directory for source files.
*/
@Parameter(readonly = true, required = true, defaultValue = " ${project.build.sourceDirectory}")
private String sourceDirectoryString;
private File sourceDirectory;
/**
* The directory for test source files.
*/
@Parameter(readonly = true, required = true, defaultValue = " ${project.build.testSourceDirectory}")
private String testSourceDirectoryString;
private File testSourceDirectory;
/**
* The directory for compiled classes.
*/
@Parameter(readonly = true, required = true, defaultValue = "${project.build.outputDirectory}")
private File outputDirectory;
/**
* The directory for compiled test classes.
*/
@Parameter(readonly = true, required = true, defaultValue = "${project.build.testOutputDirectory}")
private File testOutputDirectory;
/**
* Additional options for the container run command when dev mode starts a
* container. Takes precedence over dockerRunOpts.
*/
@Parameter(alias="containerRunOpts", property = "containerRunOpts")
private String containerRunOpts;
@Parameter(alias="dockerRunOpts", property = "dockerRunOpts")
public void setDockerRunOpts(String dockerRunOpts) {
if (dockerRunOpts != null && this.containerRunOpts != null) {
getLog().warn("Both containerRunopts and dockerRunOpts have been set. Using containerRunOpts value.");
} else {
this.containerRunOpts = dockerRunOpts;
}
}
/**
* Specify the amount of time in seconds that dev mode waits for the container
* build command to run to completion. Default to 600 seconds.
*/
@Parameter(alias="containerBuildTimeout", property = "containerBuildTimeout", defaultValue = "600")
private int containerBuildTimeout;
@Parameter(alias="dockerBuildTimeout", property = "dockerBuildTimeout")
public void setDockerBuildTimeout(int dockerBuildTimeout) {
if (dockerBuildTimeout != 600 && this.containerBuildTimeout != 600) {
getLog().warn("Both containerBuildTimeout and dockerBuildTimeout have been set. Using containerBuildTimeout value.");
} else if (dockerBuildTimeout != 600) {
this.containerBuildTimeout = dockerBuildTimeout;
}
}
/**
* If true, the default container port mappings are skipped in the container run
* command
*/
@Parameter(property = "skipDefaultPorts", defaultValue = "false")
private boolean skipDefaultPorts;
/**
* If true, preserve the temporary Containerfile/Dockerfile used in the container build command
*/
private Boolean keepTempContainerfile;
@Parameter(alias="keepTempContainerfile", property = "keepTempContainerfile")
public void setKeepTempContainerfile(Boolean keepTempContainerfile) {
this.keepTempContainerfile = keepTempContainerfile;
}
@Parameter(alias="keepTempDockerfile", property = "keepTempDockerfile", defaultValue = "false")
public void setKeepTempDockerfile(Boolean keepTempDockerfile) {
if (this.keepTempContainerfile == null) {
setKeepTempContainerfile(keepTempDockerfile);
}
}
private boolean isExplodedLooseWarApp = false;
private boolean isNewInstallation = true;
/**
* Set the container option.
*
* @param container whether dev mode should use a container
*/
protected void setContainer(boolean container) {
// set container variable for DevMojo
this.container = container;
// set project property for use in DeployMojoSupport
project.getProperties().setProperty("container", Boolean.toString(container));
}
protected List getResourceDirectories(MavenProject project, File outputDir) {
// Let's just add resources directories unconditionally, the dev util already checks the directories actually exist
// before adding them to the watch list. If we avoid checking here we allow for creating them later on.
List resourceDirs = new ArrayList();
for (Resource resource : project.getResources()) {
File resourceFile = new File(resource.getDirectory());
resourceDirs.add(resourceFile);
}
if (resourceDirs.isEmpty()) {
File defaultResourceDir = new File(project.getBasedir(), "src/main/resources");
getLog().debug("No resource directory detected, using default directory: " + defaultResourceDir);
resourceDirs.add(defaultResourceDir);
}
return resourceDirs;
}
private class DevMojoUtil extends DevUtil {
Set existingFeatures;
Map libertyDirPropertyFiles = new HashMap();
List upstreamMavenProjects;
public DevMojoUtil(File installDir, File userDir, File serverDirectory, File sourceDirectory,
File testSourceDirectory, File configDirectory, File projectDirectory, File multiModuleProjectDirectory,
List resourceDirs, JavaCompilerOptions compilerOptions, String mavenCacheLocation,
List upstreamProjects, List upstreamMavenProjects, boolean recompileDeps,
File pom, Map> parentPoms, boolean generateFeatures, boolean skipInstallFeature,
Set compileArtifactPaths, Set testArtifactPaths, List webResourceDirs) throws IOException, PluginExecutionException {
super(new File(project.getBuild().getDirectory()), serverDirectory, sourceDirectory, testSourceDirectory,
configDirectory, projectDirectory, multiModuleProjectDirectory, resourceDirs, hotTests, skipTests,
skipUTs, skipITs, skipInstallFeature, project.getArtifactId(), serverStartTimeout, verifyTimeout, verifyTimeout,
((long) (compileWait * 1000L)), libertyDebug, false, false, pollingTest, container, containerfile,
containerBuildContext, containerRunOpts, containerBuildTimeout, skipDefaultPorts, compilerOptions,
keepTempContainerfile, mavenCacheLocation, upstreamProjects, recompileDeps, project.getPackaging(),
pom, parentPoms, generateFeatures, compileArtifactPaths, testArtifactPaths, webResourceDirs);
this.libertyDirPropertyFiles = BasicSupport.getLibertyDirectoryPropertyFiles(installDir, userDir,
serverDirectory);
ServerFeatureUtil servUtil = getServerFeatureUtil(true, libertyDirPropertyFiles);
this.existingFeatures = servUtil.getServerFeatures(serverDirectory, libertyDirPropertyFiles);
this.upstreamMavenProjects = upstreamMavenProjects;
setContainerEngine(this);
}
@Override
public void debug(String msg) {
getLog().debug(msg);
}
@Override
public void debug(String msg, Throwable e) {
getLog().debug(msg, e);
}
@Override
public void debug(Throwable e) {
getLog().debug(e);
}
@Override
public void warn(String msg) {
getLog().warn(msg);
}
@Override
public void info(String msg) {
getLog().info(msg);
}
@Override
public void error(String msg) {
getLog().error(msg);
}
@Override
public void error(String msg, Throwable e) {
getLog().error(msg, e);
}
@Override
public boolean isDebugEnabled() {
return getLog().isDebugEnabled();
}
@Override
public String getServerStartTimeoutExample() {
return "'mvn liberty:dev -DserverStartTimeout=120'";
}
@Override
public String getProjectName() {
return project.getArtifactId();
}
@Override
public void libertyCreate() throws PluginExecutionException {
try {
if (isUsingBoost()) {
getLog().info("Running boost:package");
runBoostMojo("package");
} else {
runLibertyMojoCreate();
}
} catch (MojoExecutionException e) {
throw new PluginExecutionException(e);
}
}
@Override
public boolean libertyGenerateFeatures(Collection classes, boolean optimize) {
try {
if (classes != null) {
Element[] classesElem = new Element[classes.size()];
int i = 0;
for (String classPath : classes) {
classesElem[i] = element(name("classFile"), classPath);
i++;
}
// generate features for only the classFiles passed
runLibertyMojoGenerateFeatures(element(name("classFiles"), classesElem), optimize);
} else {
// pass null for classFiles so that features are generated for ALL of the
// classes
runLibertyMojoGenerateFeatures(null, optimize);
}
return true; // successfully generated features
} catch (MojoExecutionException e) {
// log errors instead of throwing an exception so we do not flood console with
// stacktrace
if (e.getCause() != null && e.getCause() instanceof PluginExecutionException) {
// PluginExecutionException indicates that the binary scanner jar could not be found
getLog().error(e.getMessage() + ".\nDisabling the automatic generation of features.");
setFeatureGeneration(false);
} else {
getLog().error(e.getMessage()
+ "\nTo disable the automatic generation of features, type 'g' and press Enter.");
}
return false;
}
}
@Override
public void libertyInstallFeature() throws PluginExecutionException {
try {
runLibertyMojoInstallFeature(null, null, container ? super.getContainerName() : null);
} catch (MojoExecutionException e) {
throw new PluginExecutionException(e);
}
}
@Override
public void libertyDeploy() throws PluginExecutionException {
try {
runLibertyMojoDeploy();
} catch (MojoExecutionException e) {
throw new PluginExecutionException(e);
}
}
@Override
public void stopServer() {
super.serverFullyStarted.set(false);
if (container) {
// TODO stop the container instead
return;
}
try {
ServerTask serverTask = initializeJava();
serverTask.setOperation("stop");
serverTask.execute();
} catch (Exception e) {
getLog().warn(MessageFormat.format(messages.getString("warn.server.stopped"), serverName));
}
}
@Override
public ServerTask getServerTask() throws Exception {
if (serverTask != null) {
return serverTask;
} else {
// Setup server task
serverTask = initializeJava();
copyConfigFiles();
serverTask.setClean(clean);
if (libertyDebug) {
setLibertyDebugPort(libertyDebugPort);
// set environment variables for server start task
serverTask.setOperation("debug");
serverTask.setEnvironmentVariables(getDebugEnvironmentVariables());
} else {
serverTask.setOperation("run");
}
return serverTask;
}
}
private Properties getPropertiesWithKeyPrefix(Properties p, String prefix) {
Properties result = new Properties();
if (p != null) {
Enumeration> e = p.propertyNames();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
if (key.startsWith(prefix)) {
result.put(key, p.get(key));
}
}
}
return result;
}
private List getEsaDependency(List dependencies) {
List deps = new ArrayList();
if (dependencies != null) {
for (Dependency d : dependencies) {
if ("esa".equals(d.getType())) {
deps.add(d);
}
}
}
return deps;
}
// returns a list of dependencies used to build the project (scope compile or provided)
private List getCompileDependency(List dependencies) {
List deps = new ArrayList();
if (dependencies != null) {
for (Dependency d : dependencies) {
if ("compile".equals(d.getScope()) || "provided".equals(d.getScope())) {
deps.add(d);
}
}
}
return deps;
}
// retun false if dependency lists are not equal, true if they are
private boolean dependencyListsEquals(List oldDeps, List deps) {
if (oldDeps.size() != deps.size()) {
return false;
}
for (int i = 0; i < oldDeps.size(); i++) {
if (!dependencyEquals(oldDeps.get(i), deps.get(i))) {
return false;
}
}
return true;
}
/**
* Determines if dependency are equal if their groupId, artifactId, version,
* type and scope are equal
*
* @param dep1
* @param dep2
* @return return false if dependency objects are not equal, true if they are
*/
private boolean dependencyEquals(Dependency dep1, Dependency dep2) {
if (!dep1.toString().equals(dep2.toString())) {
// compares groupId, artifactId, version, type
return false;
}
if (!dep1.getScope().equals(dep2.getScope())) {
return false;
}
return true;
}
private static final String LIBERTY_BOOTSTRAP_PROP = "liberty.bootstrap.";
private static final String LIBERTY_JVM_PROP = "liberty.jvm.";
private static final String LIBERTY_ENV_PROP = "liberty.env.";
private static final String LIBERTY_VAR_PROP = "liberty.var.";
private static final String LIBERTY_DEFAULT_VAR_PROP = "liberty.defaultVar.";
private boolean hasServerPropertyChanged(MavenProject project, MavenProject backupProject) {
Properties projProp = project.getProperties();
Properties backupProjProp = backupProject.getProperties();
if (!Objects.equals(getPropertiesWithKeyPrefix(projProp, LIBERTY_BOOTSTRAP_PROP),
getPropertiesWithKeyPrefix(backupProjProp, LIBERTY_BOOTSTRAP_PROP))) {
return true;
}
if (!Objects.equals(getPropertiesWithKeyPrefix(projProp, LIBERTY_JVM_PROP),
getPropertiesWithKeyPrefix(backupProjProp, LIBERTY_JVM_PROP))) {
return true;
}
if (!Objects.equals(getPropertiesWithKeyPrefix(projProp, LIBERTY_ENV_PROP),
getPropertiesWithKeyPrefix(backupProjProp, LIBERTY_ENV_PROP))) {
return true;
}
return false;
}
private boolean hasServerVariableChanged(MavenProject project, MavenProject backupProject) {
Properties projProp = project.getProperties();
Properties backupProjProp = backupProject.getProperties();
if (!Objects.equals(getPropertiesWithKeyPrefix(projProp, LIBERTY_VAR_PROP),
getPropertiesWithKeyPrefix(backupProjProp, LIBERTY_VAR_PROP))) {
return true;
}
if (!Objects.equals(getPropertiesWithKeyPrefix(projProp, LIBERTY_DEFAULT_VAR_PROP),
getPropertiesWithKeyPrefix(backupProjProp, LIBERTY_DEFAULT_VAR_PROP))) {
return true;
}
return false;
}
private boolean restartForLibertyMojoConfigChanged(Xpp3Dom config, Xpp3Dom oldConfig) {
if (!Objects.equals(config.getChild("bootstrapProperties"), oldConfig.getChild("bootstrapProperties"))) {
return true;
} else if (!Objects.equals(config.getChild("bootstrapPropertiesFile"),
oldConfig.getChild("bootstrapPropertiesFile"))) {
return true;
} else if (!Objects.equals(config.getChild("jvmOptions"), oldConfig.getChild("jvmOptions"))) {
return true;
} else if (!Objects.equals(config.getChild("jvmOptionsFile"), oldConfig.getChild("jvmOptionsFile"))) {
return true;
} else if (!Objects.equals(config.getChild("serverEnv"), oldConfig.getChild("serverEnv"))) {
return true;
} else if (!Objects.equals(config.getChild("serverEnvFile"), oldConfig.getChild("serverEnvFile"))) {
return true;
} else if (!Objects.equals(config.getChild("configDirectory"), oldConfig.getChild("configDirectory"))) {
return true;
}
return false;
}
@Override
public boolean updateArtifactPaths(ProjectModule projectModule, boolean redeployCheck, boolean generateFeatures, ThreadPoolExecutor executor)
throws PluginExecutionException {
try {
File buildFile = projectModule.getBuildFile();
if (buildFile == null) {
buildFile = this.buildFile;
}
MavenProject upstreamProject = getMavenProject(buildFile);
MavenProject backupUpstreamProject = upstreamProject;
for (MavenProject p : upstreamMavenProjects) {
if (buildFile != null && p.getFile().getCanonicalPath().equals(buildFile.getCanonicalPath())) {
backupUpstreamProject = p;
}
}
// TODO rebuild the corresponding module if the compiler options have changed
JavaCompilerOptions oldCompilerOptions = getMavenCompilerOptions(backupUpstreamProject);
JavaCompilerOptions compilerOptions = getMavenCompilerOptions(upstreamProject);
if (!oldCompilerOptions.getOptions().equals(compilerOptions.getOptions())) {
getLog().debug("Maven compiler options have been modified: " + compilerOptions.getOptions());
util.getProjectModule(buildFile).setCompilerOptions(compilerOptions);
}
Set testArtifactPaths = projectModule.getTestArtifacts();
Set compileArtifactPaths = projectModule.getCompileArtifacts();
if (this.parentBuildFiles.isEmpty()) {
compileArtifactPaths.clear();
testArtifactPaths.clear();
} else {
// remove past artifacts and add the newest calculated (covers the case where a
// dependency was deleted)
// do not clear list as it may contain dependencies from parent projects
// update classpath for dependencies changes
testArtifactPaths.removeAll(backupUpstreamProject.getTestClasspathElements());
compileArtifactPaths.removeAll(backupUpstreamProject.getCompileClasspathElements());
}
testArtifactPaths.addAll(upstreamProject.getTestClasspathElements());
compileArtifactPaths.addAll(upstreamProject.getCompileClasspathElements());
// check if project module is a parent project and update child modules' artifacts
if (!this.parentBuildFiles.isEmpty()
&& this.parentBuildFiles.containsKey(projectModule.getBuildFile().getCanonicalPath())) {
updateArtifactPaths(projectModule.getBuildFile());
}
// check if compile dependencies have changed, regenerate features and redeploy if they have
if (redeployCheck) {
// update upstream Maven projects list
int index = upstreamMavenProjects.indexOf(backupUpstreamProject);
upstreamMavenProjects.set(index, upstreamProject);
List deps = upstreamProject.getDependencies();
List oldDeps = backupUpstreamProject.getDependencies();
// detect compile dependency changes
if (!dependencyListsEquals(getCompileDependency(deps), getCompileDependency(oldDeps))) {
// optimize generate features
if (generateFeatures) {
getLog().debug("Detected a change in the compile dependencies for "
+ buildFile + " , regenerating features");
boolean generateFeaturesSuccess = libertyGenerateFeatures(null, true);
if (generateFeaturesSuccess) {
util.getJavaSourceClassPaths().clear();
}
// install new generated features, will not trigger install-feature if the feature list has not changed
util.installFeaturesToTempDir(generatedFeaturesFile, configDirectory, null,
generateFeaturesSuccess);
}
runLibertyMojoDeploy();
}
}
} catch (ProjectBuildingException | DependencyResolutionRequiredException | IOException
| MojoExecutionException e) {
getLog().error("An unexpected error occurred while processing changes in " + buildFile.getAbsolutePath()
+ ": " + e.getMessage());
getLog().debug(e);
return false;
}
return true;
}
@Override
public boolean updateArtifactPaths(File buildFile) {
try {
MavenProject parentProject = getMavenProject(buildFile);
updateChildProjectArtifactPaths(buildFile, parentProject.getCompileClasspathElements(),
parentProject.getTestClasspathElements());
} catch (ProjectBuildingException | IOException | DependencyResolutionRequiredException e) {
getLog().error("An unexpected error occurred while processing changes in " + buildFile.getAbsolutePath()
+ ": " + e.getMessage());
getLog().debug(e);
return false;
}
return true;
}
private void updateChildProjectArtifactPaths(File parentBuildFile, List compileClasspathElements,
List testClasspathElements) throws IOException, ProjectBuildingException, DependencyResolutionRequiredException {
// search for child projects
List childBuildFiles = this.parentBuildFiles.get(parentBuildFile.getCanonicalPath());
if (childBuildFiles != null) {
for (String childBuildPath : childBuildFiles) {
if (this.parentBuildFiles.containsKey(childBuildPath)) {
MavenProject project = getMavenProject(new File(childBuildPath));
if (project != null) {
compileClasspathElements.addAll(project.getCompileClasspathElements());
testClasspathElements.addAll(project.getTestClasspathElements());
}
updateChildProjectArtifactPaths(new File(childBuildPath), compileClasspathElements,
testClasspathElements);
} else {
// update artifacts on this project
Set compileArtifacts = null;
Set testArtifacts = null;
MavenProject project = null;
if (childBuildPath.equals(this.buildFile.getCanonicalPath())) {
compileArtifacts = util.getCompileArtifacts();
testArtifacts = util.getTestArtifacts();
project = getMavenProject(this.buildFile);
} else if (getProjectModule(new File(childBuildPath)) != null) {
ProjectModule projectModule = getProjectModule(new File(childBuildPath));
compileArtifacts = projectModule.getCompileArtifacts();
testArtifacts = projectModule.getTestArtifacts();
project = getMavenProject(projectModule.getBuildFile());
}
if (compileArtifacts != null && testArtifacts != null && project != null) {
compileArtifacts.clear();
testArtifacts.clear();
// TODO if a dependency is deleted from a parent project, it will still be in
// the child projects classpath elements
compileClasspathElements.addAll(project.getCompileClasspathElements());
testClasspathElements.addAll(project.getTestClasspathElements());
compileArtifacts.addAll(compileClasspathElements);
testArtifacts.addAll(testClasspathElements);
}
}
}
}
}
private MavenProject getMavenProject(File buildFile) throws ProjectBuildingException {
ProjectBuildingResult build = mavenProjectBuilder.build(buildFile,
session.getProjectBuildingRequest().setResolveDependencies(true));
MavenProject builtProject = build.getProject();
updateUpstreamProjectsArtifactPathToOutputDirectory(builtProject);
return builtProject;
}
/**
* From the project we're running dev mode from, get the artifact representing each of the upstream modules
* and make sure the artifact File is associated to the build output (target/classes, not the .m2 repo).
*
* Because of the way we dynamically build new model objects we need to do this from a given model's perspective.
*
* @param startingProject
*/
private void updateUpstreamProjectsArtifactPathToOutputDirectory(MavenProject startingProject){
Map artifactMap = startingProject.getArtifactMap();
for (MavenProject p : upstreamMavenProjects) {
Artifact projArtifact = artifactMap.get(p.getGroupId() + ":" + p.getArtifactId());
if (projArtifact != null) {
updateArtifactPathToOutputDirectory(p, projArtifact);
}
}
}
@Override
protected void updateLooseApp() throws PluginExecutionException {
// Only perform operations if we are a war type application
if (project.getPackaging().equals("war")) {
// Check if we are using an exploded loose app
if (LooseWarApplication.isExploded(project)) {
if (!isExplodedLooseWarApp) {
// The project was previously running with a "non-exploded" loose app.
// Update this flag and redeploy as an exploded loose app.
isExplodedLooseWarApp = true;
// Validate maven-war-plugin version
Plugin warPlugin = getPlugin("org.apache.maven.plugins", "maven-war-plugin");
if (!validatePluginVersion(warPlugin.getVersion(), "3.3.2")) {
getLog().warn(
"Exploded WAR functionality is enabled. Please use maven-war-plugin version 3.3.2 or greater for best results.");
}
redeployApp();
} else {
try {
runExplodedMojo();
} catch (MojoExecutionException e) {
getLog().error("Failed to run war:exploded goal", e);
}
}
} else {
if (isExplodedLooseWarApp) {
// Dev mode was previously running with an exploded loose war app. The app
// must have been updated to remove any exploded war capabilities
// (filtering, overlay, etc). Update this flag and redeploy.
isExplodedLooseWarApp = false;
redeployApp();
}
}
}
}
@Override
protected void resourceDirectoryCreated() throws IOException {
if (project.getPackaging().equals("war") && LooseWarApplication.isExploded(project)) {
try {
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "resources");
runExplodedMojo();
} catch (MojoExecutionException e) {
getLog().error("Failed to run goal(s)", e);
}
}
}
@Override
protected void resourceModifiedOrCreated(File fileChanged, File resourceParent, File outputDirectory) throws IOException {
/**
* There is an asymmetry here that we take advantage of in the exploded case. For multi-mod, this would be a copyFile, which
* does not apply Maven filters.
*/
try {
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "resources");
} catch (MojoExecutionException e) {
getLog().error("Failed to run goal(s)", e);
}
}
@Override
protected void resourceDeleted(File fileChanged, File resourceParent, File outputDirectory) throws IOException {
/**
* Why is this so asymmetric compared to resourceModifiedOrCreated() above? For two reasons: 1. The resources:resources plugin
* goal doesn't update the target/output directory with deletions, so we have to use our own custom deleteFile() method 2. In
* the case of the exploded loose app format, even having deleted the file from the outputDirectory ('target/classes'), the
* resource would typically also have been collected into the exploded 'webapp' directory. Even though it would take precedence
* in 'target/classes' when it ends up in both locations, it will still be present in the 'webapp' directory. So we re-run the
* exploded goal to force an "outdated" update cleaning this file from this location. Another approach might have been to do a
* delteFile() in the 'webapp' directory.
*/
deleteFile(fileChanged, resourceParent, outputDirectory, null);
if (project.getPackaging().equals("war") && LooseWarApplication.isExploded(project)) {
try {
runExplodedMojo();
} catch (MojoExecutionException e) {
getLog().error("Failed to run goal(s)", e);
}
}
}
@Override
public boolean recompileBuildFile(File buildFile, Set compileArtifactPaths,
Set testArtifactPaths, boolean generateFeatures, ThreadPoolExecutor executor) throws PluginExecutionException {
// monitoring project pom.xml file changes in dev mode:
// - liberty.* properties in project properties section
// - changes in liberty plugin configuration in the build plugin section
// - project dependencies changes
boolean restartServer = false;
boolean createServer = false;
boolean installFeature = false;
boolean redeployApp = false;
boolean runBoostPackage = false;
boolean optimizeGenerateFeatures = false;
ProjectBuildingResult build;
try {
build = mavenProjectBuilder.build(buildFile,
session.getProjectBuildingRequest().setResolveDependencies(true));
} catch (ProjectBuildingException e) {
getLog().error("Could not parse pom.xml. " + e.getMessage());
getLog().debug(e);
return false;
}
// set the updated project in current session;
Plugin backupLibertyPlugin = getLibertyPlugin();
Plugin backupWarPlugin = getPluginForProject("org.apache.maven.plugins", "maven-war-plugin", project);
MavenProject backupProject = project;
project = build.getProject();
session.setCurrentProject(project);
Plugin libertyPlugin = getLibertyPlugin();
Plugin warPlugin = getPluginForProject("org.apache.maven.plugins", "maven-war-plugin", project);
try {
// TODO rebuild the corresponding module if the compiler options have changed
JavaCompilerOptions oldCompilerOptions = getMavenCompilerOptions(backupProject);
JavaCompilerOptions compilerOptions = getMavenCompilerOptions(project);
if (!oldCompilerOptions.getOptions().equals(compilerOptions.getOptions())) {
getLog().debug("Maven compiler options have been modified: " + compilerOptions.getOptions());
util.updateJavaCompilerOptions(compilerOptions);
}
// Monitoring liberty properties in the pom.xml
if (hasServerPropertyChanged(project, backupProject)) {
restartServer = true;
}
if (!restartServer && hasServerVariableChanged(project, backupProject)) {
createServer = true;
}
// monitoring Liberty plugin configuration changes in dev mode
Xpp3Dom config;
Xpp3Dom oldConfig;
if (!restartServer) {
config = ExecuteMojoUtil.getPluginGoalConfig(libertyPlugin, "create", getLog());
oldConfig = ExecuteMojoUtil.getPluginGoalConfig(backupLibertyPlugin, "create", getLog());
if (!Objects.equals(config, oldConfig)) {
createServer = true;
if (restartForLibertyMojoConfigChanged(config, oldConfig)) {
restartServer = true;
}
}
}
config = ExecuteMojoUtil.getPluginGoalConfig(libertyPlugin, "install-feature", getLog());
oldConfig = ExecuteMojoUtil.getPluginGoalConfig(backupLibertyPlugin, "install-feature", getLog());
if (!Objects.equals(config, oldConfig)) {
installFeature = true;
}
config = ExecuteMojoUtil.getPluginGoalConfig(libertyPlugin, "deploy", getLog());
oldConfig = ExecuteMojoUtil.getPluginGoalConfig(backupLibertyPlugin, "deploy", getLog());
if (!Objects.equals(config, oldConfig)) {
redeployApp = true;
}
config = ExecuteMojoUtil.getPluginGoalConfig(warPlugin, "exploded", getLog());
oldConfig = ExecuteMojoUtil.getPluginGoalConfig(backupWarPlugin, "exploded", getLog());
if (!Objects.equals(config, oldConfig) || !warPlugin.getVersion().equals(backupWarPlugin.getVersion())) {
redeployApp = true;
}
config = ExecuteMojoUtil.getPluginGoalConfig(libertyPlugin, "generate-features", getLog());
oldConfig = ExecuteMojoUtil.getPluginGoalConfig(backupLibertyPlugin, "generate-features", getLog());
if (!Objects.equals(config, oldConfig)) {
optimizeGenerateFeatures = true;
}
List deps = project.getDependencies();
List oldDeps = backupProject.getDependencies();
if (!dependencyListsEquals(oldDeps, deps)) {
runBoostPackage = true;
// detect esa dependency changes
if (!dependencyListsEquals(getEsaDependency(deps), getEsaDependency(oldDeps))) {
installFeature = true;
}
// detect compile dependency changes
if (!dependencyListsEquals(getCompileDependency(deps), getCompileDependency(oldDeps))) {
// adding or removing compile dependencies (including version changes) will need
// to deploy loose app again to remove or add or update embedded libraries in
// the loose app
redeployApp = true;
optimizeGenerateFeatures = true;
}
}
// update classpath for dependencies changes
if (this.parentBuildFiles.isEmpty()) {
compileArtifactPaths.clear();
testArtifactPaths.clear();
} else {
// remove past artifacts and add the newest calculated (covers the case where a
// dependency was deleted)
// do not clear list as it may contain dependencies from parent projects
testArtifactPaths.removeAll(backupProject.getTestClasspathElements());
compileArtifactPaths.removeAll(backupProject.getCompileClasspathElements());
}
compileArtifactPaths.addAll(project.getCompileClasspathElements());
testArtifactPaths.addAll(project.getTestClasspathElements());
boolean generateFeaturesSuccess = false;
if (optimizeGenerateFeatures && generateFeatures) {
getLog().debug("Detected a change in the compile dependencies, regenerating features");
// always optimize generate features on dependency change
generateFeaturesSuccess = libertyGenerateFeatures(null, true);
if (generateFeaturesSuccess) {
util.getJavaSourceClassPaths().clear();
} else {
installFeature = false; // skip installing features if generate features fails
}
}
// We don't currently have the ability to dynamically add new directories to be watched
// There is so much that we are dynamically able to do that this could be surprising.
// For now issue a warning
Set oldMonitoredWebResourceDirs = new HashSet(this.monitoredWebResourceDirs);
Set newMonitoredWebResourceDirs = new HashSet(LooseWarApplication.getWebSourceDirectoriesToMonitor(project));
if (!oldMonitoredWebResourceDirs.equals(newMonitoredWebResourceDirs)) {
getLog().warn("Change detected in the set of filtered web resource directories, since dev mode was first launched. Adding/deleting a web resource directory has no change on the set of directories monitored by dev mode. Changing the watch list will require a dev mode restart");
}
// convert to Path, which seems to offer more reliable cross-platform, relative path comparison, then compare
Set oldResourceDirs = new HashSet();
this.resourceDirs.forEach(r -> oldResourceDirs.add(r.toPath()));
Set newResourceDirs = new HashSet();
project.getResources().forEach(r -> newResourceDirs.add(Paths.get(r.getDirectory())));
if (!oldResourceDirs.equals(newResourceDirs)) {
getLog().warn("Change detected in the set of resource directories, since dev mode was first launched. Adding/deleting a resource directory has no change on the set of directories monitored by dev mode. Changing the watch list will require a dev mode restart");
}
if (restartServer) {
// - stop Server
// - create server or runBoostMojo
// - install feature
// - deploy app
// - start server
util.restartServer();
return true;
} else {
if (isUsingBoost() && (createServer || runBoostPackage)) {
getLog().info("Running boost:package");
runBoostMojo("package");
} else if (createServer) {
runLibertyMojoCreate();
} else if (redeployApp) {
util.installFeaturesToTempDir(generatedFeaturesFile, configDirectory, null,
generateFeaturesSuccess);
runLibertyMojoDeploy();
}
if (installFeature) {
runLibertyMojoInstallFeature(null, null, super.getContainerName());
}
}
if (!(restartServer || createServer || redeployApp || installFeature || runBoostPackage)) {
// pom.xml is changed but not affecting liberty:dev mode. return true with the
// updated project set in the session
getLog().debug("changes in the pom.xml are not monitored by dev mode");
return true;
}
} catch (MojoExecutionException | DependencyResolutionRequiredException | IOException e) {
getLog().error("An unexpected error occurred while processing changes in pom.xml. " + e.getMessage());
if (installFeature) {
libertyDependencyWarning(generateFeatures, e);
}
getLog().debug(e);
project = backupProject;
session.setCurrentProject(backupProject);
return false;
}
return true;
}
// check if generateFeatures is enabled and install feature failed. If Liberty dependencies
// are in the build file display warning
private void libertyDependencyWarning(boolean generateFeatures, Exception e) {
if (generateFeatures && !getEsaDependency(project.getDependencies()).isEmpty()
&& e.getMessage().contains(InstallFeatureUtil.CONFLICT_MESSAGE)) {
getLog().warn(GEN_FEAT_LIBERTY_DEP_WARNING);
}
}
@Override
public void installFeatures(File configFile, File serverDir, boolean generateFeatures) {
try {
ServerFeatureUtil servUtil = getServerFeatureUtil(true, libertyDirPropertyFiles);
Set features = servUtil.getServerFeatures(serverDir, libertyDirPropertyFiles);
if (features != null) {
Set featuresCopy = new HashSet(features);
if (existingFeatures != null) {
features.removeAll(existingFeatures);
// check if features have been removed
Set existingFeaturesCopy = new HashSet(existingFeatures);
existingFeaturesCopy.removeAll(featuresCopy);
if (!existingFeaturesCopy.isEmpty()) {
getLog().info("Configuration features have been removed: " + existingFeaturesCopy);
}
}
// check if features have been added and install new features
if (!features.isEmpty()) {
getLog().info("Configuration features have been added: " + features);
// pass all new features to install-feature as backup in case the serverDir cannot be accessed
Element[] featureElems = new Element[features.size() + 1];
featureElems[0] = element(name("acceptLicense"), "true");
String[] values = features.toArray(new String[features.size()]);
for (int i = 0; i < features.size(); i++) {
featureElems[i + 1] = element(name("feature"), values[i]);
}
runLibertyMojoInstallFeature(element(name("features"), featureElems), serverDir, super.getContainerName());
}
}
} catch (MojoExecutionException e) {
getLog().error("Failed to install features from configuration file", e);
libertyDependencyWarning(generateFeatures, e);
}
}
@Override
public ServerFeatureUtil getServerFeatureUtilObj() {
// suppress logs from ServerFeatureUtil so that dev console is not flooded
return getServerFeatureUtil(true, libertyDirPropertyFiles);
}
@Override
public Set getExistingFeatures() {
return this.existingFeatures;
}
@Override
public void updateExistingFeatures() {
ServerFeatureUtil servUtil = getServerFeatureUtil(true, libertyDirPropertyFiles);
Set features = servUtil.getServerFeatures(serverDirectory, libertyDirPropertyFiles);
existingFeatures = features;
}
@Override
public boolean compile(File dir) {
try {
if (dir.equals(sourceDirectory)) {
runCompileMojoLogWarning();
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "resources");
}
if (dir.equals(testSourceDirectory)) {
runTestCompileMojoLogWarning();
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "testResources");
}
return true;
} catch (MojoExecutionException e) {
getLog().error("Unable to compile", e);
return false;
}
}
@Override
public boolean compile(File dir, ProjectModule project) {
MavenProject mavenProject = resolveMavenProject(project.getBuildFile());
try {
if (dir.equals(project.getSourceDirectory())) {
runCompileMojoLogWarning(mavenProject);
runMojoForProject("org.apache.maven.plugins", "maven-resources-plugin", "resources", mavenProject);
}
if (dir.equals(project.getTestSourceDirectory())) {
runTestCompileMojoLogWarning(mavenProject);
runMojoForProject("org.apache.maven.plugins", "maven-resources-plugin", "testResources", mavenProject);
}
return true;
} catch (MojoExecutionException e) {
getLog().error("Unable to compile", e);
return false;
}
}
@Override
public void runUnitTests(File buildFile) throws PluginExecutionException, PluginScenarioException {
MavenProject currentProject = resolveMavenProject(buildFile);
try {
runTestMojo("org.apache.maven.plugins", "maven-surefire-plugin", "test", currentProject);
runTestMojo("org.apache.maven.plugins", "maven-surefire-report-plugin", "report-only", currentProject);
} catch (MojoExecutionException e) {
Throwable cause = e.getCause();
if (cause != null && cause instanceof MojoFailureException) {
throw new PluginScenarioException("Unit tests failed: " + cause.getLocalizedMessage(), e);
} else {
throw new PluginExecutionException("Failed to run unit tests", e);
}
}
}
@Override
public void runIntegrationTests(File buildFile) throws PluginExecutionException, PluginScenarioException {
MavenProject currentProject = resolveMavenProject(buildFile);
try {
runTestMojo("org.apache.maven.plugins", "maven-failsafe-plugin", "integration-test", currentProject);
runTestMojo("org.apache.maven.plugins", "maven-surefire-report-plugin", "failsafe-report-only", currentProject);
runTestMojo("org.apache.maven.plugins", "maven-failsafe-plugin", "verify", currentProject);
} catch (MojoExecutionException e) {
Throwable cause = e.getCause();
if (cause != null && cause instanceof MojoFailureException) {
throw new PluginScenarioException("Integration tests failed: " + cause.getLocalizedMessage(), e);
} else {
throw new PluginExecutionException("Failed to run integration tests", e);
}
}
}
@Override
public void redeployApp() throws PluginExecutionException {
try {
runLibertyMojoDeploy();
} catch (MojoExecutionException e) {
throw new PluginExecutionException("liberty:deploy goal failed:" + e.getMessage());
}
}
@Override
public boolean isLooseApplication() {
// dev mode forces deploy with looseApplication=true, but it only takes effect
// if packaging is one of the supported loose app types
return DeployMojoSupport.isSupportedLooseAppType(project.getPackaging());
}
@Override
public File getLooseApplicationFile() {
return getLooseAppConfigFile(project, container);
}
}
private boolean isUsingBoost() {
return boostPlugin != null;
}
private void doDevMode() throws MojoExecutionException {
String mvnVersion = runtime.getMavenVersion();
getLog().debug("Maven version: " + mvnVersion);
// Maven 3.8.2 and 3.8.3 contain a bug where compile artifacts are not resolved
// correctly in threads. Block dev mode from running on these versions
if (mvnVersion.equals("3.8.2") || mvnVersion.equals("3.8.3")) {
throw new MojoExecutionException("Detected Maven version " + mvnVersion
+ ". This version is not supported for dev mode. Upgrade to Maven 3.8.4 or higher to use dev mode.");
}
boolean isEar = false;
if (project.getPackaging().equals("ear")) {
isEar = true;
}
// If there are downstream projects (e.g. other modules depend on this module in the Maven Reactor build order),
// then skip dev mode on this module but only run compile.
List upstreamMavenProjects = new ArrayList();
ProjectDependencyGraph graph = session.getProjectDependencyGraph();
if (graph != null) {
checkMultiModuleConflicts(graph);
List downstreamProjects = graph.getDownstreamProjects(project, true);
if (!downstreamProjects.isEmpty()) {
getLog().debug("Downstream projects: " + downstreamProjects);
if (isEar) {
runMojo("org.apache.maven.plugins", "maven-ear-plugin", "generate-application-xml");
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "resources");
getOrCreateEarArtifact(project);
} else if (project.getPackaging().equals("pom")) {
getLog().debug("Skipping compile/resources on module with pom packaging type");
} else {
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "resources");
runCompileMojoLogWarning();
}
return;
} else {
// get all upstream projects
upstreamMavenProjects.addAll(graph.getUpstreamProjects(project, true));
}
if (containsPreviousLibertyModule(graph)) {
// skip this module
return;
}
}
// get all parent poms
Map> parentPoms = new HashMap>();
for (MavenProject proj : graph.getAllProjects()) {
updateParentPoms(parentPoms, proj);
}
// default behavior of recompileDependencies
if (recompileDependencies == null) {
if (upstreamMavenProjects.isEmpty()) {
// single module project default to false
getLog().debug(
"The recompileDependencies parameter was not explicitly set. The default value -DrecompileDependencies=false will be used.");
recompileDependencies = "false";
} else {
// multi module project default to true
getLog().debug(
"The recompileDependencies parameter was not explicitly set. The default value for multi module projects -DrecompileDependencies=true will be used.");
recompileDependencies = "true";
}
}
boolean recompileDeps = Boolean.parseBoolean(recompileDependencies);
if (recompileDeps) {
if (!upstreamMavenProjects.isEmpty()) {
getLog().info("The recompileDependencies parameter is set to \"true\". On a file change all dependent modules will be recompiled.");
} else {
getLog().info("The recompileDependencies parameter is set to \"true\". On a file change the entire project will be recompiled.");
}
} else {
getLog().info("The recompileDependencies parameter is set to \"false\". On a file change only the affected classes will be recompiled.");
}
// Check if this is a Boost application
boostPlugin = project.getPlugin("org.microshed.boost:boost-maven-plugin");
processContainerParams();
if (serverDirectory.exists()) {
if (ServerStatusUtil.isServerRunning(installDirectory, super.outputDirectory, serverName)) {
if (!container) {
throw new MojoExecutionException("The server " + serverName
+ " is already running. Terminate all instances of the server before starting dev mode."
+ " You can stop a server instance with the command 'mvn liberty:stop'.");
} else {
getLog().warn("Running server detected, which could cause unexpected results. To terminate the local running server, run the command 'mvn liberty:stop'. Also, the warning may occur because a previous server execution did not stop cleanly, in which case you may want to run 'mvn clean' before re-running");
}
}
}
// create an executor for tests with an additional queue of size 1, so
// any further changes detected mid-test will be in the following run
final ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue(1, true));
if (isEar) {
runMojo("org.apache.maven.plugins", "maven-ear-plugin", "generate-application-xml");
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "resources");
} else if (project.getPackaging().equals("pom")) {
getLog().debug("Skipping compile/resources on module with pom packaging type");
} else {
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "resources");
runCompileMojoLogWarning();
runMojo("org.apache.maven.plugins", "maven-resources-plugin", "testResources");
runTestCompileMojoLogWarning();
}
sourceDirectory = new File(sourceDirectoryString.trim());
testSourceDirectory = new File(testSourceDirectoryString.trim());
ArrayList javaFiles = new ArrayList();
listFiles(sourceDirectory, javaFiles, ".java");
ArrayList javaTestFiles = new ArrayList();
listFiles(testSourceDirectory, javaTestFiles, ".java");
getLog().debug("Source directory: " + sourceDirectory);
getLog().debug("Output directory: " + outputDirectory);
getLog().debug("Test Source directory: " + testSourceDirectory);
getLog().debug("Test Output directory: " + testOutputDirectory);
if (isUsingBoost()) {
getLog().info("Running boost:package");
runBoostMojo("package");
} else {
if (generateFeatures) {
// generate features on startup - provide all classes and only user specified
// features to binary scanner
try {
String generatedFileCanonicalPath;
try {
generatedFileCanonicalPath = new File(configDirectory,
BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).getCanonicalPath();
} catch (IOException e) {
generatedFileCanonicalPath = new File(configDirectory,
BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).toString();
}
getLog().warn(
"The source configuration directory will be modified. Features will automatically be generated in a new file: "
+ generatedFileCanonicalPath);
runLibertyMojoGenerateFeatures(null, true);
} catch (MojoExecutionException e) {
if (e.getCause() != null && e.getCause() instanceof PluginExecutionException) {
// PluginExecutionException indicates that the binary scanner jar could not be found
getLog().error(e.getMessage() + ".\nDisabling the automatic generation of features.");
generateFeatures = false;
} else {
throw new MojoExecutionException(e.getMessage()
+ " To disable the automatic generation of features, start dev mode with -DgenerateFeatures=false.",
e);
}
}
}
runLibertyMojoCreate();
// If non-container, install features before starting server. Otherwise, user
// should have "RUN features.sh" in their Containerfile/Dockerfile if they want features to be
// installed.
// Added check here for the new skip install feature parameter.
// Need to also check if this is a new Liberty installation or not. The isNewInstallation flag is set by runLibertyMojoCreate.
if (!container && (!skipInstallFeature || isNewInstallation)) {
runLibertyMojoInstallFeature(null, null, null);
} else if (skipInstallFeature) {
getLog().info("Skipping installation of features due to skipInstallFeature configuration.");
}
runLibertyMojoDeploy();
}
if (project.getPackaging().equals("war")) {
// Check if we are using the exploded loose app functionality and save for checking later on.
isExplodedLooseWarApp = LooseWarApplication.isExploded(project);
// Validate maven-war-plugin version
if (isExplodedLooseWarApp) {
Plugin warPlugin = getPlugin("org.apache.maven.plugins", "maven-war-plugin");
if (!validatePluginVersion(warPlugin.getVersion(), "3.3.2")) {
getLog().warn("Exploded WAR functionality is enabled. Please use maven-war-plugin version 3.3.2 or greater for best results.");
}
}
}
// resource directories
List resourceDirs = getResourceDirectories(project, outputDirectory);
List webResourceDirs = LooseWarApplication.getWebSourceDirectoriesToMonitor(project);
JavaCompilerOptions compilerOptions = getMavenCompilerOptions(project);
// collect upstream projects
List upstreamProjects = new ArrayList();
if (!upstreamMavenProjects.isEmpty()) {
for (MavenProject p : upstreamMavenProjects) {
// get compiler options for upstream project
JavaCompilerOptions upstreamCompilerOptions = getMavenCompilerOptions(p);
Set compileArtifacts = new HashSet();
Set testArtifacts = new HashSet();
Build build = p.getBuild();
File upstreamSourceDir = new File(build.getSourceDirectory());
File upstreamOutputDir = new File(build.getOutputDirectory());
File upstreamTestSourceDir = new File(build.getTestSourceDirectory());
File upstreamTestOutputDir = new File(build.getTestOutputDirectory());
// resource directories
List upstreamResourceDirs = getResourceDirectories(p, upstreamOutputDir);
// properties that are set in the pom file
Properties props = p.getProperties();
// properties that are set by user via CLI parameters
Properties userProps = session.getUserProperties();
Plugin libertyPlugin = getLibertyPluginForProject(p);
// use "dev" goal, although we don't expect the skip tests flags to be bound to any goal
Xpp3Dom config = ExecuteMojoUtil.getPluginGoalConfig(libertyPlugin, "dev", getLog());
boolean upstreamSkipTests = DevHelper.getBooleanFlag(config, userProps, props, "skipTests");
boolean upstreamSkipITs = DevHelper.getBooleanFlag(config, userProps, props, "skipITs");
boolean upstreamSkipUTs = DevHelper.getBooleanFlag(config, userProps, props, "skipUTs");
// only force skipping unit test for ear modules otherwise honour existing skip
// test params
if (p.getPackaging().equals("ear")) {
upstreamSkipUTs = true;
}
// build list of dependent modules
List dependentProjects = graph.getDownstreamProjects(p, true);
List dependentModules = new ArrayList();
for (MavenProject depProj : dependentProjects) {
dependentModules.add(depProj.getFile());
}
ProjectModule upstreamProject = new ProjectModule(p.getFile(), p.getArtifactId(), p.getPackaging(),
compileArtifacts, testArtifacts, upstreamSourceDir, upstreamOutputDir, upstreamTestSourceDir,
upstreamTestOutputDir, upstreamResourceDirs, upstreamSkipTests, upstreamSkipUTs,
upstreamSkipITs, upstreamCompilerOptions, dependentModules);
upstreamProjects.add(upstreamProject);
}
}
// skip unit tests for ear applications
if (isEar) {
skipUTs = true;
}
// pom.xml
File pom = project.getFile();
try {
// collect artifacts canonical paths in order to build classpath
Set compileArtifactPaths = new HashSet(project.getCompileClasspathElements());
Set testArtifactPaths = new HashSet(project.getTestClasspathElements());
util = new DevMojoUtil(installDirectory, userDirectory, serverDirectory, sourceDirectory, testSourceDirectory,
configDirectory, project.getBasedir(), multiModuleProjectDirectory, resourceDirs, compilerOptions,
settings.getLocalRepository(), upstreamProjects, upstreamMavenProjects, recompileDeps, pom, parentPoms,
generateFeatures, skipInstallFeature, compileArtifactPaths, testArtifactPaths, webResourceDirs);
} catch (IOException | PluginExecutionException |DependencyResolutionRequiredException e) {
throw new MojoExecutionException("Error initializing dev mode.", e);
}
util.addShutdownHook(executor);
try {
util.startServer();
} catch (PluginExecutionException e) {
throw new MojoExecutionException("Error starting the server in dev mode.", e);
}
// start watching for keypresses immediately
util.runHotkeyReaderThread(executor);
// Note that serverXmlFile can be null. DevUtil will automatically watch
// all files in the configDirectory,
// which is where the server.xml is located if a specific serverXmlFile
// configuration parameter is not specified.
try {
util.watchFiles(outputDirectory, testOutputDirectory, executor, serverXmlFile, bootstrapPropertiesFile,
jvmOptionsFile);
} catch (Exception e) {
if (e.getMessage() != null) {
// a proper message is included in the exception if the server has been stopped
// by another process
getLog().info(e.getMessage());
}
return; // enter shutdown hook
}
}
@Override
public void execute() throws MojoExecutionException {
init();
if (skip) {
getLog().info("\nSkipping dev goal.\n");
return;
}
doDevMode();
}
/**
* Update map with list of parent poms and their subsequent child poms
*
* @param parentPoms Map of parent poms and subsequent child poms
* @param proj MavenProject
*/
private void updateParentPoms(Map> parentPoms, MavenProject proj) {
MavenProject parentProject = proj.getParent();
try {
if (parentProject != null) {
// append to existing list
if (parentProject.getFile() != null) {
List childPoms = parentPoms.get(parentProject.getFile().getCanonicalPath());
if (childPoms == null) {
childPoms = new ArrayList();
childPoms.add(proj.getFile().getCanonicalPath());
parentPoms.put(parentProject.getFile().getCanonicalPath(), childPoms);
} else {
if (!childPoms.contains(proj.getFile().getCanonicalPath())) {
childPoms.add(proj.getFile().getCanonicalPath());
}
}
if (parentProject.getParent() != null) {
// recursively search for top most parent project
updateParentPoms(parentPoms, parentProject);
}
}
}
} catch (IOException e) {
getLog().error("An unexpected error occurred when trying to resolve " + proj.getFile() + ": " + e.getMessage());
getLog().debug(e);
}
}
private JavaCompilerOptions getMavenCompilerOptions(MavenProject currentProject) {
Plugin plugin = getPluginForProject("org.apache.maven.plugins", "maven-compiler-plugin", currentProject);
Xpp3Dom configuration = ExecuteMojoUtil.getPluginGoalConfig(plugin, "compile", getLog());
JavaCompilerOptions compilerOptions = new JavaCompilerOptions();
String showWarnings = getCompilerOption(configuration, "showWarnings", "maven.compiler.showWarnings", currentProject);
if (showWarnings != null) {
boolean showWarningsBoolean = Boolean.parseBoolean(showWarnings);
getLog().debug("Setting showWarnings to " + showWarningsBoolean);
compilerOptions.setShowWarnings(showWarningsBoolean);
}
String source = getCompilerOption(configuration, "source", "maven.compiler.source", currentProject);
if (source != null) {
getLog().debug("Setting compiler source to " + source);
compilerOptions.setSource(source);
}
String target = getCompilerOption(configuration, "target", "maven.compiler.target", currentProject);
if (target != null) {
getLog().debug("Setting compiler target to " + target);
compilerOptions.setTarget(target);
}
String release = getCompilerOption(configuration, "release", "maven.compiler.release", currentProject);
if (release != null) {
getLog().debug("Setting compiler release to " + release);
compilerOptions.setRelease(release);
}
String encoding = getCompilerOption(configuration, "encoding", "project.build.sourceEncoding", currentProject);
if (encoding != null) {
getLog().debug("Setting compiler encoding to " + encoding);
compilerOptions.setEncoding(encoding);
}
return compilerOptions;
}
/**
* Gets a compiler option's value from CLI parameters, maven-compiler-plugin's
* configuration or project properties.
*
* @param configuration The maven-compiler-plugin's configuration from
* pom.xml
* @param mavenParameterName The maven-compiler-plugin parameter name to look
* for
* @param projectPropertyName The project property name to look for, if the
* mavenParameterName's parameter could not be found
* in the plugin configuration.
* @param currentProject The current Maven Project
* @return The compiler option
*/
private String getCompilerOption(Xpp3Dom configuration, String mavenParameterName, String projectPropertyName,
MavenProject currentProject) {
String option = null;
// CLI parameter takes precedence over plugin configuration
option = session.getUserProperties().getProperty(projectPropertyName);
// Plugin configuration takes precedence over project property
if (option == null && configuration != null) {
Xpp3Dom child = configuration.getChild(mavenParameterName);
if (child != null) {
option = child.getValue();
}
}
if (option == null) {
option = currentProject.getProperties().getProperty(projectPropertyName);
}
return option;
}
private void processContainerParams() throws MojoExecutionException {
if (container) {
// this also sets the project property for use in DeployMojoSupport
setContainer(true);
}
}
private MavenProject resolveMavenProject(File buildFile) {
ProjectBuildingResult build;
MavenProject currentProject = project; // default to main project
try {
if (buildFile != null && !project.getFile().getCanonicalPath().equals(buildFile.getCanonicalPath())) {
build = mavenProjectBuilder.build(buildFile,
session.getProjectBuildingRequest().setResolveDependencies(true));
// if we can resolve the project associated with build file, run tests on
// corresponding project
if (build.getProject() != null) {
currentProject = build.getProject();
}
}
} catch (ProjectBuildingException | IOException e) {
getLog().error("An unexpected error occurred when trying to run integration tests for "
+ buildFile.getAbsolutePath() + ": " + e.getMessage());
getLog().debug(e);
}
return currentProject;
}
private void runTestMojo(String groupId, String artifactId, String goal, MavenProject project)
throws MojoExecutionException {
Plugin plugin = getPluginForProject(groupId, artifactId, project);
Xpp3Dom config = ExecuteMojoUtil.getPluginGoalConfig(plugin, goal, getLog());
// check if this is a project module or main module
if (util.isMultiModuleProject()) {
try {
Set testArtifacts;
ProjectModule projectModule = util.getProjectModule(project.getFile());
if (projectModule != null) {
testArtifacts = projectModule.getTestArtifacts();
} else {
// assume this is the main module
testArtifacts = util.getTestArtifacts();
}
if (goal.equals("test") || goal.equals("integration-test")) {
injectClasspathElements(config, testArtifacts, project.getTestClasspathElements());
}
} catch (IOException | DependencyResolutionRequiredException e) {
getLog().error(
"Unable to resolve test artifact paths for " + project.getFile() + ". Restart dev mode to ensure classpaths are properly resolved.");
getLog().debug(e);
}
}
if (goal.equals("test")) {
injectTestId(config);
} else if (goal.equals("integration-test")) {
injectTestId(config);
injectLibertyProperties(config);
// clean up previous summary file
File summaryFile = null;
Xpp3Dom summaryFileElement = config.getChild("summaryFile");
if (summaryFileElement != null && summaryFileElement.getValue() != null) {
summaryFile = new File(summaryFileElement.getValue());
} else {
summaryFile = new File(project.getBuild().getDirectory(), "failsafe-reports/failsafe-summary.xml");
}
try {
getLog().debug("Looking for summary file at " + summaryFile.getCanonicalPath());
} catch (IOException e) {
getLog().debug("Unable to resolve summary file " + e.getMessage());
}
if (summaryFile.exists()) {
boolean deleteResult = summaryFile.delete();
getLog().debug("Summary file deleted? " + deleteResult);
} else {
getLog().debug("Summary file doesn't exist");
}
} else if (goal.equals("failsafe-report-only")) {
Plugin failsafePlugin = getPluginForProject("org.apache.maven.plugins", "maven-failsafe-plugin", project);
Xpp3Dom failsafeConfig = ExecuteMojoUtil.getPluginGoalConfig(failsafePlugin, "integration-test", getLog());
Xpp3Dom linkXRef = new Xpp3Dom("linkXRef");
if (failsafeConfig != null) {
Xpp3Dom reportsDirectoryElement = failsafeConfig.getChild("reportsDirectory");
if (reportsDirectoryElement != null) {
Xpp3Dom reportDirectories = new Xpp3Dom("reportsDirectories");
reportDirectories.addChild(reportsDirectoryElement);
config.addChild(reportDirectories);
}
linkXRef = failsafeConfig.getChild("linkXRef");
if (linkXRef == null) {
linkXRef = new Xpp3Dom("linkXRef");
}
}
linkXRef.setValue("false");
config.addChild(linkXRef);
} else if (goal.equals("report-only")) {
Plugin surefirePlugin = getPluginForProject("org.apache.maven.plugins", "maven-surefire-plugin", project);
Xpp3Dom surefireConfig = ExecuteMojoUtil.getPluginGoalConfig(surefirePlugin, "test", getLog());
Xpp3Dom linkXRef = new Xpp3Dom("linkXRef");
if (surefireConfig != null) {
Xpp3Dom reportsDirectoryElement = surefireConfig.getChild("reportsDirectory");
if (reportsDirectoryElement != null) {
Xpp3Dom reportDirectories = new Xpp3Dom("reportsDirectories");
reportDirectories.addChild(reportsDirectoryElement);
config.addChild(reportDirectories);
}
linkXRef = surefireConfig.getChild("linkXRef");
if (linkXRef == null) {
linkXRef = new Xpp3Dom("linkXRef");
}
}
linkXRef.setValue("false");
config.addChild(linkXRef);
}
getLog().debug("POM file: " + project.getFile() + "\n" + groupId + ":" + artifactId + " " + goal
+ " configuration:\n" + config);
MavenSession tempSession = session.clone();
tempSession.setCurrentProject(project);
executeMojo(plugin, goal(goal), config, executionEnvironment(project, tempSession, pluginManager));
}
/**
* Inject missing test artifacts (usually from upstream modules) for Maven
* surefire and failsafe plugins
*
* @param config The configuration element
* @param testArtifacts The complete list of test artifacts resolved
* from the build file and upstream build files
* @param testClasspathElements The list of test artifacts resolved from the
* build file
*/
private void injectClasspathElements(Xpp3Dom config, Set testArtifacts,
List testClasspathElements) {
if (testArtifacts.size() > testClasspathElements.size()) {
List additionalClassPathElements = new ArrayList();
additionalClassPathElements.addAll(testArtifacts);
additionalClassPathElements.removeAll(testClasspathElements);
Xpp3Dom classpathElement = config.getChild("additionalClasspathElements");
if (classpathElement == null) {
classpathElement = new Xpp3Dom("additionalClasspathElements");
}
for (String element : additionalClassPathElements) {
Xpp3Dom childElem = new Xpp3Dom("additionalClasspathElement");
childElem.setValue(element);
classpathElement.addChild(childElem);
}
config.addChild(classpathElement);
}
}
/**
* Force change a property so that the checksum calculated by
* AbstractSurefireMojo is different every time.
*
* @param config The configuration element
*/
private void injectTestId(Xpp3Dom config) {
Xpp3Dom properties = config.getChild("properties");
if (properties == null || properties.getChild(TEST_RUN_ID_PROPERTY_NAME) == null) {
Element e = element(name("properties"), element(name(TEST_RUN_ID_PROPERTY_NAME), String.valueOf(runId++)));
config.addChild(e.toDom());
} else {
properties.getChild(TEST_RUN_ID_PROPERTY_NAME).setValue(String.valueOf(runId++));
}
}
/**
* Add Liberty system properties for tests to consume.
*
* @param config The configuration element
* @throws MojoExecutionException if the userDirectory canonical path cannot be
* resolved
*/
private void injectLibertyProperties(Xpp3Dom config) throws MojoExecutionException {
Xpp3Dom sysProps = config.getChild("systemPropertyVariables");
if (sysProps == null) {
Element e = element(name("systemPropertyVariables"));
sysProps = e.toDom();
config.addChild(sysProps);
}
// don't overwrite existing properties if they are already defined
addDomPropertyIfNotFound(sysProps, LIBERTY_HOSTNAME, util.getHostName());
addDomPropertyIfNotFound(sysProps, LIBERTY_HTTP_PORT, util.getHttpPort());
addDomPropertyIfNotFound(sysProps, LIBERTY_HTTPS_PORT, util.getHttpsPort());
addDomPropertyIfNotFound(sysProps, MICROSHED_HOSTNAME, util.getHostName());
addDomPropertyIfNotFound(sysProps, MICROSHED_HTTP_PORT, util.getHttpPort());
addDomPropertyIfNotFound(sysProps, MICROSHED_HTTPS_PORT, util.getHttpsPort());
try {
addDomPropertyIfNotFound(sysProps, WLP_USER_DIR_PROPERTY_NAME, userDirectory.getCanonicalPath());
} catch (IOException e) {
throw new MojoExecutionException(
"Could not resolve canonical path of userDirectory parameter: " + userDirectory.getAbsolutePath(),
e);
}
}
private void addDomPropertyIfNotFound(Xpp3Dom sysProps, String key, String value) {
if (sysProps.getChild(key) == null && value != null) {
sysProps.addChild(element(name(key), value).toDom());
}
}
private void runBoostMojo(String goal) throws MojoExecutionException {
MavenProject boostProject = this.project;
MavenSession boostSession = this.session;
getLog().debug("plugin version: " + boostPlugin.getVersion());
executeMojo(boostPlugin, goal(goal), configuration(),
executionEnvironment(boostProject, boostSession, pluginManager));
}
private void listFiles(File directory, List files, String suffix) {
if (directory != null) {
// Get all files from a directory.
File[] fList = directory.listFiles();
if (fList != null) {
for (File file : fList) {
if (file.isFile() && ((suffix == null) || (file.getName().toLowerCase().endsWith("." + suffix)))) {
files.add(file);
} else if (file.isDirectory()) {
listFiles(file, files, suffix);
}
}
}
}
}
/**
* Executes Maven goal passed but sets failOnError to false All errors are
* logged as warning messages
*
* @param goal Maven compile goal
* @param MavenProject Maven project to run compile goal against, null if
* default project is to be used
* @throws MojoExecutionException
*/
private void runCompileMojo(String goal, MavenProject mavenProject) throws MojoExecutionException {
Plugin plugin = getPluginForProject("org.apache.maven.plugins", "maven-compiler-plugin", mavenProject);
MavenSession tempSession = session.clone();
tempSession.setCurrentProject(mavenProject);
MavenProject tempProject = mavenProject;
Xpp3Dom config = ExecuteMojoUtil.getPluginGoalConfig(plugin, goal, getLog());
config = Xpp3Dom.mergeXpp3Dom(configuration(element(name("failOnError"), "false")), config);
getLog().info("Running maven-compiler-plugin:" + goal + " on " + tempProject.getFile());
getLog().debug("configuration:\n" + config);
executeMojo(plugin, goal(goal), config, executionEnvironment(tempProject, tempSession, pluginManager));
}
/**
* Executes maven:compile but logs errors as warning messages
*
* @throws MojoExecutionException
*/
private void runCompileMojoLogWarning() throws MojoExecutionException {
runCompileMojo("compile", project);
updateArtifactPathToOutputDirectory(project);
}
/**
* Executes maven:compile but logs errors as warning messages
*
* @throws MojoExecutionException
*/
private void runCompileMojoLogWarning(MavenProject mavenProject) throws MojoExecutionException {
runCompileMojo("compile", mavenProject);
updateArtifactPathToOutputDirectory(mavenProject);
}
/**
* Executes maven:testCompile but logs errors as warning messages
*
* @throws MojoExecutionException
*/
private void runTestCompileMojoLogWarning() throws MojoExecutionException {
runCompileMojo("testCompile", project);
}
/**
* Executes maven:testCompile but logs errors as warning messages
*
* @throws MojoExecutionException
*/
private void runTestCompileMojoLogWarning(MavenProject mavenProject) throws MojoExecutionException {
runCompileMojo("testCompile", mavenProject);
}
/**
* Executes liberty:install-feature unless using Liberty in a container
*
* @throws MojoExecutionException
*/
@Override
protected void runLibertyMojoInstallFeature(Element features, File serverDir, String containerName) throws MojoExecutionException {
super.runLibertyMojoInstallFeature(features, serverDir, containerName);
}
/**
* Executes liberty:create unless using a container, then just create the
* necessary server directories
*
* @throws MojoExecutionException
*/
@Override
protected void runLibertyMojoCreate() throws MojoExecutionException {
if (container) {
getLog().debug("runLibertyMojoCreate check for installDirectory and serverDirectory");
if (!installDirectory.isDirectory()) {
installDirectory.mkdirs();
}
if (!serverDirectory.isDirectory()) {
serverDirectory.mkdirs();
}
} else {
// Check to see if Liberty was already installed and set flag accordingly.
if (installDirectory != null) {
try {
File installDirectoryCanonicalFile = installDirectory.getCanonicalFile();
// Quick check to see if a Liberty installation exists at the installDirectory
File file = new File(installDirectoryCanonicalFile, "lib/ws-launch.jar");
if (file.exists()) {
this.isNewInstallation = false;
}
} catch (IOException e) {
}
}
super.runLibertyMojoCreate();
}
}
/**
* Executes liberty:generate-features.
*
* @throws MojoExecutionException
*/
@Override
protected void runLibertyMojoGenerateFeatures(Element classFiles, boolean optimize) throws MojoExecutionException {
super.runLibertyMojoGenerateFeatures(classFiles, optimize);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy