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

com.devonfw.cobigen.api.util.MavenUtil Maven / Gradle / Ivy

The newest version!
package com.devonfw.cobigen.api.util;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zeroturnaround.exec.ProcessExecutor;
import org.zeroturnaround.exec.ProcessResult;
import org.zeroturnaround.exec.StartedProcess;
import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;

import com.devonfw.cobigen.api.constants.MavenConstants;
import com.devonfw.cobigen.api.exception.CobiGenRuntimeException;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;

/**
 * Utils to operate with maven artifacts
 */
public class MavenUtil {

  /** Logger instance. */
  private static final Logger LOG = LoggerFactory.getLogger(MavenUtil.class);

  /**
   * Executes a Maven class path build command which will download all the transitive dependencies needed for the CLI
   *
   * @param pomFile POM file that defines the needed CobiGen dependencies to build
   * @param cpFile the cpFile to be created
   */
  public static void cacheMavenClassPath(Path pomFile, Path cpFile) {

    cleanupExistingClassPathCaches(cpFile);

    LOG.info("Calculating class path for {} and downloading the needed maven dependencies. Please be patient...",
        pomFile);
    List args = Lists.newArrayList(SystemUtil.determineMvnPath().toString(), "dependency:build-classpath",
        "-Dmdep.outputFile=" + cpFile.toString());
    if (pomFile.getFileSystem().provider().getClass().getSimpleName().equals("ZipFileSystemProvider")) {
      pomFile = createCachedPomFromJar(pomFile, cpFile.getParent());
      // just add this command in case your are working on jar
      // otherwise, pom resolution will fail if you work on a general maven module
      args.add("-f");
      args.add(pomFile.toString());
    }
    runCommand(pomFile.getParent(), args);
    LOG.debug("Downloaded dependencies successfully.");
  }

  /**
   * Resolve all maven dependencies of given project
   *
   * @param mvnProjectRoot the maven project root
   */
  public static void resolveDependencies(Path mvnProjectRoot) {

    LOG.info(
        "Resolving maven dependencies for maven project {} to be able to make use of reflection in templates. Please be patient...",
        mvnProjectRoot);
    List args = Lists.newArrayList(SystemUtil.determineMvnPath().toString(), "dependency:resolve");
    runCommand(mvnProjectRoot, args);
    LOG.debug("Downloaded dependencies successfully.");
  }

  /**
   * Resolve all maven dependencies of given pom.xml inside given project
   *
   * @param pomFile to cache and resolve from
   *
   * @param mvnProjectRoot the maven project root
   */
  public static void resolveDependencies(Path pomFile, Path mvnProjectRoot) {

    LOG.info(
        "Resolving maven dependencies for maven project {} to be able to make use of reflection in templates. Please be patient...",
        mvnProjectRoot);

    if (pomFile.getFileSystem().provider().getClass().getSimpleName().equals("ZipFileSystemProvider")) {
      pomFile = createCachedPomFromJar(pomFile, mvnProjectRoot);
    }

    List args = Lists.newArrayList(SystemUtil.determineMvnPath().toString());
    args.add("-f");
    args.add(pomFile.toString());
    args.add("dependency:resolve");
    runCommand(mvnProjectRoot, args);
    LOG.debug("Downloaded dependencies successfully.");
  }

  /**
   * Generates a cached-pom.xml file from a jar archive, automatically deletes the cached-pom.xml on exit
   *
   * @param pomFile to cache
   * @param outputPath output directory
   * @return the cached-pom.xml file
   */
  public static Path createCachedPomFromJar(Path pomFile, Path outputPath) {

    Path cachedPomXml = outputPath.resolve("cached-pom.xml");
    try {
      Files.copy(pomFile, cachedPomXml);
    } catch (IOException e) {
      throw new CobiGenRuntimeException("Unable to extract " + pomFile.toUri() + " from JAR to " + cachedPomXml, e);
    }
    pomFile = cachedPomXml;
    cachedPomXml.toFile().deleteOnExit();
    return cachedPomXml;
  }

  /**
   * Adds URLs from class paths cache file to URLClassLoader. If no class paths cache file was found a new one will be
   * generated.
   *
   * @param classPathCacheFile the class paths cache file to read/create
   * @param pomFile POM file that defines the needed CobiGen dependencies to build
   * @param parentClassLoader parent ClassLoader
   *
   * @return URLClassLoader
   */
  public static URLClassLoader addURLsFromCachedClassPathsFile(Path classPathCacheFile, Path pomFile,
      ClassLoader parentClassLoader) {

    if (!Files.exists(classPathCacheFile)) {
      LOG.debug("Building class paths for maven configuration ...");
      cacheMavenClassPath(pomFile, classPathCacheFile);
    } else {
      LOG.debug("Taking cached class paths from: {}", classPathCacheFile);
    }

    try (Stream fileLinesStream = Files.lines(classPathCacheFile)) {
      URL[] classPathEntries = fileLinesStream
          .flatMap(e -> Arrays.stream(e.split(SystemUtil.getOS().contains("win") ? ";" : ":"))).map(path -> {
            try {
              return new File(path).toURI().toURL();
            } catch (MalformedURLException e) {
              LOG.error("URL of class path entry {} is malformed", path, e);
            }
            return null;
          }).toArray(size -> new URL[size]);

      return new URLClassLoader(classPathEntries, parentClassLoader);
    } catch (IOException e) {
      throw new CobiGenRuntimeException("Unable to read " + classPathCacheFile, e);
    }
  }

  /**
   * Generates a hash for the provided POM file
   *
   * @param pomFile to generate hash from
   * @return String generated hash
   */
  public static String generatePomFileHash(Path pomFile) {

    String pomFileHash;
    try {
      pomFileHash = ByteSource.wrap(Files.readAllBytes(pomFile)).hash(Hashing.murmur3_128()).toString();
    } catch (IOException e) {
      LOG.warn("Could not calculate hash of {}", pomFile.toUri());
      pomFileHash = "";
    }
    return pomFileHash;
  }

  /**
   * @return the maven repository path
   */
  public static Path determineMavenRepositoryPath() {

    LOG.info("Determine maven repository path");
    String m2Repo = runCommand(SystemUtils.getUserHome().toPath(),
        Lists.newArrayList(SystemUtil.determineMvnPath().toString(), "help:evaluate",
            "-Dexpression=settings.localRepository", "-DforceStdout"));
    LOG.debug("Determined {} as maven repository path.", m2Repo);
    return Paths.get(m2Repo);
  }

  /**
   * Execute any command in the given execution directory with the given arguments
   *
   * @param execDir the execution directory the command should run in
   * @param args the maven arguments to execute
   * @return the process output
   */
  private static String runCommand(Path execDir, List args) {

    // https://stackoverflow.com/a/66801171
    args.add("-Djansi.force=true");
    args.add("-Djansi.passthrough=true");
    args.add("-B");
    args.add("-q");
    args.add("-Dorg.slf4j.simpleLogger.defaultLogLevel=" + (LOG.isDebugEnabled() ? "DEBUG" : "INFO"));
    args.add("-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN");

    try {
      StartedProcess process = new ProcessExecutor().readOutput(true).destroyOnExit().directory(execDir.toFile())
          .environment("MAVEN_OPTS", replaceAllUnixPathsOnWin(System.getenv("MAVEN_OPTS")))
          .environment("M2_REPO", replaceAllUnixPathsOnWin(System.getenv("M2_REPO"))).command(args)
          .redirectError(
              Slf4jStream.of(LoggerFactory.getLogger(MavenUtil.class.getName() + "." + "dep-build")).asError())
          .redirectOutput(
              Slf4jStream.of(LoggerFactory.getLogger(MavenUtil.class.getName() + "." + "dep-build")).asInfo())
          .start();

      Future future = process.getFuture();
      ProcessResult processResult = future.get();

      if (processResult.getExitValue() != 0) {
        LOG.error("Error while getting all the needed transitive dependencies. Please check your internet connection.");
        throw new CobiGenRuntimeException("Unable to build cobigen dependencies");
      }
      return processResult.getOutput().getString("UTF-8");
    } catch (InterruptedException | ExecutionException | IOException e) {
      throw new CobiGenRuntimeException("Unable to build cobigen dependencies", e);
    }
  }

  /**
   * @return the string while all absolute unix paths in the string are converted to windows absolute paths if running
   *         on windows OS
   */
  private static String replaceAllUnixPathsOnWin(String string) {

    if (StringUtils.isNotEmpty(string)) {
      Pattern p = Pattern.compile("=((/[^/]+)+/?)");
      Matcher matcher = p.matcher(string);
      StringBuffer sb = new StringBuffer();

      while (matcher.find()) {
        matcher.appendReplacement(sb,
            "=" + SystemUtil.convertUnixPathToWinOnWin(matcher.group(1)).toString().replace("\\", "\\\\"));
      }
      matcher.appendTail(sb);
      string = sb.toString();
    }
    return string;
  }

  /**
   * Cleanup old classpath cache files
   *
   * @param cpFile classpath file location
   */
  private static void cleanupExistingClassPathCaches(Path cpFile) {

    Path rootPath = cpFile.getParent();
    try {
      Files.walk(rootPath)
          .filter(p -> p.getFileName().startsWith(MavenConstants.CLASSPATH_CACHE_FILE.replace("%s.txt", "")))
          .forEach(p -> {
            try {
              Files.delete(p);
            } catch (IOException e) {
              LOG.warn("Failed cleaning up old classpath cache file {}", p);
            }
          });
    } catch (IOException e) {
      LOG.warn("Failed cleaning up old classpath cache files in {}", rootPath);
    }
  }

  /**
   * Tries to find a pom.xml file in the passed folder
   *
   * @param source folder where we check if a pom.xml file is found
   * @param foundPom whether a pom has already been detected
   * @param topLevel whether to find top-level pom only
   * @return the pom.xml file if it was found, null otherwise
   */
  private static Path findPom(Path source, boolean foundPom, boolean topLevel) {

    if (source == null) {
      return null;
    }

    if (Files.isRegularFile(source)) {
      if (source.getFileName().toString().equals(MavenConstants.POM)) {
        if (Files.exists(source)) {
          LOG.info("{} is already a pom.xml.");
          return source;
        }
      }
      LOG.warn("File {} neither exists nor is a {}, trying to search for {} in one of the parent folders.", source,
          MavenConstants.POM, MavenConstants.POM);
      return findPom(source.getParent(), foundPom, topLevel);
    } else {
      Path pomFile = source.resolve(MavenConstants.POM);
      if (Files.exists(pomFile) && Files.isRegularFile(pomFile)) {
        // try searching another parent
        if (!topLevel || findPom(source.getParent(), false, topLevel) == null) {
          LOG.debug("Stop searching pom as no pom.xml in parent directory. Taking {} as top-level pom.xml", pomFile);
          return pomFile;
        }
      }

      if (foundPom) {
        return null;
      } else {
        return findPom(source.getParent(), foundPom, topLevel);
      }
    }
  }

  /**
   * Tries to find the root folder of the project in order to build the classpath. This method is trying to find the
   * first pom.xml file and then getting the folder where is located
   *
   * @param inputFile passed by the user
   * @param topLevel whether to find top-level pom only
   * @return the project folder
   *
   */
  public static Path getProjectRoot(Path inputFile, boolean topLevel) {

    Path pomFile = findPom(inputFile, false, topLevel);
    if (pomFile != null) {
      LOG.info("Found project root in path {}", pomFile.getParent());
      return pomFile.getParent();
    }
    LOG.debug("Project root could not be found.");
    return null;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy