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

io.takari.maven.testing.executor.Embedded3xLauncher Maven / Gradle / Ivy

There is a newer version: 3.0.5
Show newest version
/**
 * Copyright (c) 2014 Takari, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package io.takari.maven.testing.executor;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to you 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.
 */

import static io.takari.maven.testing.executor.MavenInstallationUtils.SYSPROP_MAVEN_HOME;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import org.codehaus.plexus.classworlds.ClassWorldException;
import org.codehaus.plexus.classworlds.launcher.ConfigurationException;
import org.codehaus.plexus.classworlds.launcher.ConfigurationHandler;
import org.codehaus.plexus.classworlds.launcher.ConfigurationParser;
import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;


/**
 * Launches an embedded Maven 3.x instance from some Maven installation directory.
 * 
 * @author Benjamin Bentmann
 */
class Embedded3xLauncher implements MavenLauncher {

  private static class ClassworldsConfiguration implements ConfigurationHandler {

    private String mainType;
    private String mainRealm;
    private LinkedHashMap> realms = new LinkedHashMap<>();
    private List curEntries;

    @Override
    public void setAppMain(String mainType, String mainRealm) {
      this.mainType = mainType;
      this.mainRealm = mainRealm;
    }

    @Override
    public void addRealm(String realm) throws DuplicateRealmException {
      if (!realms.containsKey(realm)) {
        curEntries = new ArrayList<>();
        realms.put(realm, curEntries);
      }
    }

    @Override
    public void addImportFrom(String relamName, String importSpec) throws NoSuchRealmException {
      throw new UnsupportedOperationException();
    }

    @Override
    public void addLoadFile(File file) {
      if (curEntries == null) {
        throw new IllegalStateException();
      }
      curEntries.add(file.getAbsolutePath());
    }

    public void addEntries(String realm, List locations) {
      List entries = realms.get(realm);
      if (entries == null) {
        throw new IllegalStateException();
      }
      entries.addAll(0, locations);
    }

    @Override
    public void addLoadURL(URL url) {
      if (curEntries == null) {
        throw new IllegalStateException();
      }
      curEntries.add(url.toExternalForm());
    }

    public void store(OutputStream os) throws IOException {
      BufferedWriter out = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); //$NON-NLS-1$
      out.write(String.format("main is %s from %s\n", mainType, mainRealm));
      for (Map.Entry> realm : realms.entrySet()) {
        out.write(String.format("[%s]\n", realm.getKey()));
        for (String entry : realm.getValue()) {
          out.write(String.format("load %s\n", entry));
        }
      }
      out.flush();
    }

  }

  private static class Key {

    private final File mavenHome;
    private final File classworldConf;
    private final List bootclasspath;
    private final List extensions;
    private final List args;

    public Key(File mavenHome, File classworldConf, List bootclasspath, List extensions, List args) {
      this.mavenHome = mavenHome;
      this.classworldConf = classworldConf;
      this.bootclasspath = clone(bootclasspath);
      this.extensions = clone(extensions);
      this.args = clone(args);
    }

    @Override
    public int hashCode() {
      int hash = 17;
      hash = hash * 31 + mavenHome.hashCode();
      hash = hash * 31 + (classworldConf != null ? classworldConf.hashCode() : 0);
      hash = hash * 31 + (bootclasspath != null ? bootclasspath.hashCode() : 0);
      hash = hash * 31 + (extensions != null ? extensions.hashCode() : 0);
      hash = hash * 31 + (args != null ? args.hashCode() : 0);
      return hash;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof Key)) {
        return false;
      }
      Key other = (Key) obj;
      return eq(mavenHome, other.mavenHome) && eq(classworldConf, other.classworldConf) && eq(bootclasspath, other.bootclasspath) && eq(extensions, other.extensions) && eq(args, other.args);
    }

    private static  List clone(List origin) {
      return origin != null ? new ArrayList<>(origin) : null;
    }

    private static  boolean eq(T a, T b) {
      return a != null ? a.equals(b) : b == null;
    }
  }

  private static final Map CACHE = new HashMap<>();

  private final File mavenHome;

  private final Object classWorld;

  private final Object mavenCli;

  private final Method doMain;

  private final List args;

  private Embedded3xLauncher(File mavenHome, Object classWorld, Object mavenCli, Method doMain, List args) {
    this.mavenHome = mavenHome;
    this.classWorld = classWorld;
    this.mavenCli = mavenCli;
    this.doMain = doMain;
    this.args = args;
  }

  /**
   * Launches an embedded Maven 3.x instance from some Maven installation directory.
   */
  public static Embedded3xLauncher createFromMavenHome(File mavenHome, File classworldConf, List extensions, List args) throws LauncherException {
    if (!isValidMavenHome(mavenHome)) {
      throw new LauncherException("Invalid Maven home directory " + mavenHome);
    }

    List bootclasspath = toClasspath(System.getProperty("maven.bootclasspath"));

    Properties originalProperties = copy(System.getProperties());
    System.setProperty(SYSPROP_MAVEN_HOME, mavenHome.getAbsolutePath());

    try {
      final Key key = new Key(mavenHome, classworldConf, bootclasspath, extensions, args);
      Embedded3xLauncher launcher = CACHE.get(key);
      if (launcher == null) {
        launcher = createFromMavenHome0(mavenHome, classworldConf, bootclasspath, extensions, args);
        CACHE.put(key, launcher);
      }
      return launcher;
    } finally {
      System.setProperties(originalProperties);
    }
  }

  private static boolean isValidMavenHome(File mavenHome) {
    if (mavenHome == null) {
      return false;
    }

    if ("WORKSPACE".equals(mavenHome.getPath()) || "EMBEDDED".equals(mavenHome.getPath())) {
      return true;
    }

    return mavenHome.isDirectory();
  }

  private static List toClasspath(String string) throws LauncherException {
    if (string == null) {
      return null;
    }
    StringTokenizer st = new StringTokenizer(string, File.pathSeparator);
    List classpath = new ArrayList<>();
    while (st.hasMoreTokens()) {
      try {
        classpath.add(new File(st.nextToken()).toURI().toURL());
      } catch (MalformedURLException e) {
        throw new LauncherException("Invalid launcher classpath " + string, e);
      }
    }
    return classpath;
  }

  private static Embedded3xLauncher createFromMavenHome0(File mavenHome, File classworldConf, List bootclasspath, List extensions, List args) throws LauncherException {
    File configFile = MavenInstallationUtils.getClassworldsConf(mavenHome, classworldConf);

    ClassLoader bootLoader = getBootLoader(mavenHome, bootclasspath);

    ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(bootLoader);
    try {
      ClassworldsConfiguration config = new ClassworldsConfiguration();
      ConfigurationParser configParser = new ConfigurationParser(config, System.getProperties());
      try (InputStream is = new BufferedInputStream(new FileInputStream(configFile))) {
        configParser.parse(is);
      }
      if (extensions != null && !extensions.isEmpty()) {
        config.addEntries("plexus.core", extensions);
      }
      ByteArrayOutputStream buf = new ByteArrayOutputStream();
      config.store(buf);

      // Launcher launcher = new org.codehaus.plexus.classworlds.launcher.Launcher()
      // launcher.configure(buf)
      // ClassWorld classWorld = launcher.getWorld()
      // MavenCli mavenCli = launcher.getMainClass().newInstance(classWorld)

      Class launcherClass = bootLoader.loadClass("org.codehaus.plexus.classworlds.launcher.Launcher");
      Object launcher = launcherClass.newInstance();
      launcherClass.getMethod("configure", new Class[] {InputStream.class}).invoke(launcher, new ByteArrayInputStream(buf.toByteArray()));
      Object classWorld = launcherClass.getMethod("getWorld").invoke(launcher);

      Class cliClass = (Class) launcherClass.getMethod("getMainClass").invoke(launcher);
      Object mavenCli = cliClass.getConstructor(classWorld.getClass()).newInstance(classWorld);

      Method doMain = cliClass.getMethod("doMain", //
          String[].class, String.class, PrintStream.class, PrintStream.class);

      return new Embedded3xLauncher(mavenHome, classWorld, mavenCli, doMain, args);
    } catch (ReflectiveOperationException | IOException | ClassWorldException | ConfigurationException e) {
      throw new LauncherException("Invalid Maven home directory " + mavenHome, e);
    } finally {
      Thread.currentThread().setContextClassLoader(oldClassLoader);
    }
  }

  private static ClassLoader getBootLoader(File mavenHome, List classpath) {
    List urls = classpath;

    if (urls == null) {
      urls = new ArrayList();

      File bootDir = new File(mavenHome, "boot");
      addUrls(urls, bootDir);
    }

    if (urls.isEmpty()) {
      throw new IllegalArgumentException("Invalid Maven home directory " + mavenHome);
    }

    URL[] ucp = urls.toArray(new URL[urls.size()]);

    return new URLClassLoader(ucp, ClassLoader.getSystemClassLoader().getParent());
  }

  private static void addUrls(List urls, File directory) {
    File[] jars = directory.listFiles();

    if (jars != null) {
      for (int i = 0; i < jars.length; i++) {
        File jar = jars[i];

        if (jar.getName().endsWith(".jar")) {
          try {
            urls.add(jar.toURI().toURL());
          } catch (MalformedURLException e) {
            throw (RuntimeException) new IllegalStateException().initCause(e);
          }
        }
      }
    }
  }

  @Override
  public int run(String[] cliArgs, File multiModuleProjectDirectory, File workingDirectory, File logFile) throws IOException, LauncherException {
    PrintStream out = (logFile != null) ? new PrintStream(new FileOutputStream(logFile)) : System.out;
    try {
      Properties originalProperties = copy(System.getProperties());
      System.setProperties(null);
      System.setProperty(SYSPROP_MAVEN_HOME, mavenHome.getAbsolutePath());
      System.setProperty("user.dir", workingDirectory.getAbsolutePath());
      System.setProperty("maven.multiModuleProjectDirectory", multiModuleProjectDirectory.getAbsolutePath());

      ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(mavenCli.getClass().getClassLoader());
      try {
        Set origRealms = getRealmIds();

        List args = new ArrayList<>(this.args);
        args.addAll(Arrays.asList(cliArgs));

        out.format("Maven Executor implementation: %s\n", getClass().getName());
        out.format("Maven home: %s\n", mavenHome);
        out.format("Build work directory: %s\n", workingDirectory);
        out.format("Execution parameters: %s\n\n", args);

        Object result = doMain.invoke(mavenCli, //
            args.toArray(new String[args.size()]), workingDirectory.getAbsolutePath(), out, out);

        Set realms = getRealmIds();
        realms.removeAll(origRealms);
        for (String realmId : realms) {
          disposeRealm(realmId);
        }

        return ((Number) result).intValue();
      } finally {
        Thread.currentThread().setContextClassLoader(originalClassLoader);

        System.setProperties(originalProperties);
      }
    } catch (IllegalAccessException e) {
      throw new LauncherException("Failed to run Maven: " + e.getMessage(), e);
    } catch (InvocationTargetException e) {
      throw new LauncherException("Failed to run Maven: " + e.getMessage(), e);
    } finally {
      if (logFile != null) {
        out.close();
      }
    }
  }

  private static Properties copy(Properties properties) {
    Properties copy = new Properties();
    for (String key : properties.stringPropertyNames()) {
      copy.put(key, properties.getProperty(key));
    }
    return copy;
  }

  @Override
  public String getMavenVersion() throws LauncherException {
    try {
      String version = MavenInstallationUtils.getMavenVersion(mavenCli.getClass());
      if (version != null) {
        return version;
      }
    } catch (IOException e) {
      throw new LauncherException("Failed to read Maven version", e);
    }

    throw new LauncherException("Could not determine embedded Maven version");
  }

  private Set getRealmIds() {
    Set result = new HashSet<>();

    try {
      Collection realms = (Collection) classWorld.getClass().getMethod("getRealms").invoke(classWorld);
      for (Object realm : realms) {
        String id = (String) realm.getClass().getMethod("getId").invoke(realm);
        result.add(id);
      }
    } catch (RuntimeException | ReflectiveOperationException e) {
      // best-effort, silently ignore failures
    }

    return result;
  }

  private void disposeRealm(String id) {
    try {
      classWorld.getClass().getMethod("disposeRealm", String.class).invoke(classWorld, id);
    } catch (RuntimeException | ReflectiveOperationException e) {
      // best-effort, silently ignore failures
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy