package ch.sbb.maven.plugins.iib.mojos;
import static ch.sbb.maven.plugins.iib.utils.PomXmlUtils.getModel;
import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
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.groupId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.name;
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.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.BuildPluginManager;
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.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.codehaus.plexus.util.FileUtils;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import ch.sbb.maven.plugins.iib.utils.DependencyPredicate;
/**
* Unpacks the dependent WebSphere Message Broker Projects.
*
* Implemented with help from: https://github.com/TimMoore/mojo-executor/blob/master/README.md
*
* requiresDependencyResolution below is required for the unpack-dependencies goal to work correctly. See https://github.com/TimMoore/mojo-executor/issues/3
*/
@Mojo(name = "prepare-bar-build-workspace", requiresDependencyResolution = ResolutionScope.TEST)
public class PrepareBarBuildWorkspaceMojo extends AbstractMojo {
/**
* a comma separated list of dependency types to be unpacked
*/
private static final String UNPACK_IIB_DEPENDENCY_TYPES = "zip";
private static final String UNPACK_IIB_DEPENDENCY_SCOPE = "compile";
/**
* Whether classloaders are in use with this bar
*/
@Parameter(property = "iib.useClassloaders", defaultValue = "false", required = true)
protected Boolean useClassloaders;
/**
* The path of the workspace in which the projects are extracted to be built.
*/
@Parameter(property = "iib.workspace", defaultValue = "${project.build.directory}/iib/workspace", required = true)
protected File workspace;
/**
* List of remote repositories to be used by the plugin to resolve dependencies.
*/
@Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true)
protected List<RemoteRepository> remoteRepos;
/**
* The Maven Project Object
*/
@Parameter(property = "project", required = true, readonly = true)
protected MavenProject project;
/**
* The Maven Session Object
*/
@Parameter(property = "session", required = true, readonly = true)
protected MavenSession session;
/**
* The Maven PluginManager Object
*/
@Component
protected BuildPluginManager buildPluginManager;
/**
* The entry point to Aether, i.e. the component doing all the work.
*/
@Component
protected RepositorySystem repoSystem;
/**
* The current repository/network configuration of Maven.
*/
@Parameter(property = "repositorySystemSession")
protected RepositorySystemSession repoSession;
public void execute() throws MojoExecutionException, MojoFailureException {
// unpack the iib-src dependencies
unpackIibDependencies();
getLog().info("Copying jar dependencies into iib-src directories...");
for (String dependencyDirectory : getDependencyDirectories()) {
File pomfile = new File(dependencyDirectory + "/pom.xml");
Model model = getModel(pomfile);
// MavenProject dependencyProject = new MavenProject(model);
// if classloaders are not in use, copy any transient dependencies of type jar into the dependency directory
if (!useClassloaders) {
// copyJarDependencies also deletes the "pom.xml"
copyJarDependencies(dependencyDirectory, "pom.xml");
}
Collection<Dependency> filtered = CollectionUtils.select(project.getDependencies(), new DependencyPredicate("artifactId", model.getArtifactId()));
if (filtered.size() == 0) {
deleteFile(pomfile);
}
}
}
/**
* @param dependencyDirectory
* @param pomFilename
* @throws MojoExecutionException
* @throws MojoFailureException
*/
private void copyJarDependencies(String dependencyDirectory, String pomFilename) throws MojoExecutionException, MojoFailureException {
File pomFile = new File(dependencyDirectory, pomFilename);
// optimise performance by quickly checking if there's a dependency of type jar before kicking off the maven copy-dependencies (sub-)build
for (Dependency dependency : getRuntimeJarDependencies(pomFile)) {
ArtifactResult jarArtifactResult = resolveArtifact(dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(), dependency.getVersion());
ArtifactResult pomArtifactResult = resolveArtifact(dependency.getGroupId(), dependency.getArtifactId(), "pom", dependency.getVersion());
String tmpPomFilename = "." + pomArtifactResult.getArtifact().getArtifactId() + "-" + pomArtifactResult.getArtifact().getFile().getName();
try {
FileUtils.copyFile(jarArtifactResult.getArtifact().getFile(), new File(dependencyDirectory, jarArtifactResult.getArtifact().getFile().getName()));
FileUtils.copyFile(pomArtifactResult.getArtifact().getFile(), new File(dependencyDirectory, tmpPomFilename));
} catch (IOException e) {
throw new MojoExecutionException("Error copying dependency from " + jarArtifactResult.getArtifact().getFile().getAbsolutePath() + " to " + dependencyDirectory, e);
}
// TODO this could potentially be optimised to copy-dependencies in one shot instead of each transient jar separately
copyJarDependencies(dependencyDirectory, tmpPomFilename);
}
}
/**
* deletes a file than should be closed, but may not yet have been garbage collected
*
* @param fileToDelete the file that should be deleted
*/
private void deleteFile(File fileToDelete) {
fileToDelete.delete();
if (fileToDelete.exists()) {
// couldn't delete it yet. Collect garbage, sleep a while & try again
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// fail silently
}
fileToDelete.delete();
if (fileToDelete.exists()) {
getLog().warn("Cannot delete file: " + fileToDelete);
}
}
}
private void flattenPom(File pomFile) {
InvocationRequest request = new DefaultInvocationRequest();
request.setJavaHome(new File(System.getProperty("java.home")));
request.setPomFile(pomFile);
List<String> goals = new ArrayList<String>();
goals.add("org.codehaus.mojo:flatten-maven-plugin:1.0.0-beta-5:flatten");
goals.add("-f");
goals.add(pomFile.getAbsolutePath());
// if maven debugging is not enabled, run the flattening in quiet mode
if (!getLog().isDebugEnabled()) {
goals.add("--quiet");
}
request.setGoals(goals);
Invoker invoker = new DefaultInvoker();
try {
invoker.execute(request);
invoker = null;
} catch (MavenInvocationException e)
{
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private List<String> getDependencyDirectories() throws MojoExecutionException {
List<String> dependencyDirectories;
try {
dependencyDirectories = FileUtils.getDirectoryNames(workspace, "*", ".*", true);
} catch (IOException e1) {
throw new MojoExecutionException("Error searching for dependent project directories under: " + workspace.getAbsolutePath());
}
return dependencyDirectories;
}
/**
* returns the directly referenced jar dependencies required at runtime
*
* @param pomFile
* @return
* @throws MojoExecutionException
*/
private List<Dependency> getRuntimeJarDependencies(File pomFile) throws MojoExecutionException {
List<Dependency> runtimeJars = new ArrayList<Dependency>();
// do a quick analysis of the pom file to see if it has runtime jar dependencies
boolean mustResolve = false;
Model model = getModel(pomFile);
for (Dependency dependency : model.getDependencies()) {
if (isRuntimeJarDependency(dependency)) {
mustResolve = true;
}
}
// runtime jar dependencies
if (mustResolve) {
// flatten the pom (expanding variables) into .flattened-pom.xml
flattenPom(pomFile);
// now get the model from the flattened pomFile
File flattenedPomFile = new File(pomFile.getParentFile(), ".flattened-pom.xml");
Model flattenedModel = getModel(flattenedPomFile);
List<Dependency> dependencies = flattenedModel.getDependencies();
for (Dependency dependency : dependencies) {
if (isRuntimeJarDependency(dependency)) {
runtimeJars.add(dependency);
}
}
deleteFile(flattenedPomFile);
}
return runtimeJars;
}
/**
* @param dependency
* @return
*/
private boolean isRuntimeJarDependency(Dependency dependency) {
// if it's a jar dependency
if (dependency.getType() == null || "jar".equals(dependency.getType())) {
// if it's a compile or runtime dependency
if (dependency.getScope() == null || "compile".equals(dependency.getScope()) || "runtime".equals(dependency.getScope())) {
return true;
}
}
return false;
}
private ArtifactResult resolveArtifact(String groupId, String artifactId, String extension, String version) throws MojoFailureException
{
Artifact artifact;
try
{
artifact = new DefaultArtifact(groupId, artifactId, extension, version);
} catch (IllegalArgumentException e)
{
throw new MojoFailureException(e.getMessage(), e);
}
ArtifactRequest artifactRequest = new ArtifactRequest();
artifactRequest.setArtifact(artifact);
artifactRequest.setRepositories(remoteRepos);
getLog().debug("Resolving artifact " + artifact + " from " + remoteRepos);
ArtifactResult result;
try
{
result = repoSystem.resolveArtifact(repoSession, artifactRequest);
} catch (ArtifactResolutionException e)
{
throw new MojoFailureException(e.getMessage(), e);
}
getLog().debug("Resolved artifact " + artifact + " to " + result.getArtifact().getFile() + " from "
+ result.getRepository());
return result;
}
/**
* unpacks dependencies of a given scope to the specified directory
*
* @throws MojoExecutionException
*/
private void unpackIibDependencies() throws MojoExecutionException {
// define the directory to be unpacked into and create it
workspace.mkdirs();
// unpack all IIB dependencies that match the given scope (compile)
executeMojo(plugin(groupId("org.apache.maven.plugins"), artifactId("maven-dependency-plugin"), version("2.8")), goal("unpack-dependencies"), configuration(element(name("outputDirectory"),
workspace.getAbsolutePath()), element(name("includeTypes"), UNPACK_IIB_DEPENDENCY_TYPES), element(name("includeScope"), UNPACK_IIB_DEPENDENCY_SCOPE)),
executionEnvironment(project, session, buildPluginManager));
// delete the dependency-maven-plugin-markers directory
try {
FileUtils.deleteDirectory(new File(project.getBuild().getDirectory(), "dependency-maven-plugin-markers"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}