Package org.sonatype.nexus.test.booter

Source Code of org.sonatype.nexus.test.booter.EmbeddedNexusBooter

/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2007-2014 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.test.booter;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.sonatype.nexus.proxy.maven.routing.internal.ConfigImpl;

import com.google.common.base.Throwables;
import org.apache.commons.io.FileUtils;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.commons.io.filefilter.FileFilterUtils.filter;
import static org.apache.commons.io.filefilter.FileFilterUtils.suffixFileFilter;

/**
* Embedded Nexus server booter.
*
* @since 2.8
*/
public class EmbeddedNexusBooter
    implements NexusBooter
{
  private static Logger log = LoggerFactory.getLogger(EmbeddedNexusBooter.class);

  private static final String IT_REALM_ID = "it-realm";

  private final File installDir;

  private final Map<String,String> overrides;

  private final ClassWorld world;

  private final ClassRealm bootRealm;

  private final Class<?> launcherClass;

  private final Constructor launcherFactory;

  private Object launcher;

  public EmbeddedNexusBooter(final File installDir, final int port) throws Exception {
    this.installDir = checkNotNull(installDir).getCanonicalFile();
    log.info("Install directory: {}", installDir);

    // HACK: This is non-standard setup by the test-enviroinment (AbstractEnvironmentMojo)
    File workDir = new File(installDir, "../sonatype-work").getCanonicalFile();
    log.info("Work directory: {}", workDir);

    checkArgument(port > 1024);
    log.info("Port: {}", port);

    overrides = new HashMap<>();
    overrides.put("application-port", String.valueOf(port));
    overrides.put("nexus-base", installDir.getPath());
    overrides.put("nexus-work", workDir.getPath());

    // force bootstrap logback configuration
    overrides.put("logback.configurationFile", new File(installDir, "etc/logback.xml").getPath());

    // Make sure H2 uses TCCL for Java deserialization
    overrides.put("h2.useThreadContextClassLoader", "true");

    // Making MI integration in Nexus behave in-sync
    overrides.put("org.sonatype.nexus.events.IndexerManagerEventInspector.async", Boolean.FALSE.toString());

    // Disable autorouting initialization prevented
    overrides.put(ConfigImpl.FEATURE_ACTIVE_KEY, Boolean.FALSE.toString());

    // Karaf configuration
    String base = installDir.getCanonicalPath();
    overrides.put("karaf.base", base);
    overrides.put("karaf.home", base);
    overrides.put("karaf.data", base + File.separatorChar + "data");
    overrides.put("karaf.etc", base + File.separatorChar + "etc");
    overrides.put("karaf.instances", base + File.separatorChar + "instances");
    overrides.put("karaf.startLocalConsole", "false");
    overrides.put("karaf.startRemoteShell", "false");
    overrides.put("karaf.clean.cache", "true");

    // disable Karaf's random shutdown port as we don't use it when testing
    overrides.put("karaf.shutdown.port", "-1");

    // move tmp under sonatype-work to avoid contamination between tests
    overrides.put("java.io.tmpdir", new File(workDir, "tmp").getPath());

    log.info("Overrides:");
    for (Entry<String,String> entry : overrides.entrySet()) {
      log.info("  {}='{}'", entry.getKey(), entry.getValue());
    }

    tamperJettyConfiguration();

    world = new ClassWorld();
    bootRealm = createBootRealm();

    launcherClass = bootRealm.loadClass("org.apache.karaf.main.Main");
    log.info("Launcher class: {}", launcherClass);

    launcherFactory = launcherClass.getConstructor(String[].class);
    log.info("Launcher factory: {}", launcherFactory);
  }

  private void tamperJettyConfiguration() throws IOException {
    final File file = new File(installDir, "etc/jetty.xml");
    String xml = FileUtils.readFileToString(file, "UTF-8");

    // Disable the shutdown hook, since it disturbs the embedded work
    // In Jetty8, any invocation of server.stopAtShutdown(boolean) will create a thread in a class static member.
    // Hence, we simply want to make sure, that there is NO invocation happening of that method.
    // FIXME: These can be avoided by using a <Property> configuration with default value in jetty.xml
    xml = xml.replace(
        "<Set name=\"stopAtShutdown\">true</Set>",
        "<!-- Set name=\"stopAtShutdown\">true</Set -->"
    );

    // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=357318#c62
    if (System.getProperty("os.name").toLowerCase().contains("windows")) {
      xml = xml.replace(
          "org.sonatype.nexus.bootstrap.jetty.InstrumentedSelectChannelConnector",
          "org.eclipse.jetty.server.nio.BlockingChannelConnector"
      );
    }

    FileUtils.writeStringToFile(file, xml, "UTF-8");
  }

  private ClassRealm createBootRealm() throws Exception {
    List<URL> classpath = new ArrayList<>() ;

    File confDir = new File(installDir, "etc");
    log.info("Boot conf dir: {}", confDir);
    classpath.add(confDir.toURI().toURL());

    File libDir = new File(installDir, "lib");
    log.info("Boot lib dir: {}", libDir);
    File[] jars = filter(suffixFileFilter(".jar"), libDir.listFiles());
    for (File jar : jars) {
      classpath.add(jar.toURI().toURL());
    }

    ClassRealm realm = world.newRealm("it-boot", null);
    log.info("Boot classpath:");
    for (URL url : classpath) {
      log.info("  {}", url);
      realm.addURL(url);
    }

    return realm;
  }

  @Override
  public void startNexus(final String testId) throws Exception {
    checkState(launcher == null, "Nexus already started");

    final ClassLoader cl = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(bootRealm);
    String[] args = {};

    try {
      System.getProperties().putAll(overrides);

      // capture boot/launcher logging per-test
      File karafLog = new File(installDir, "../../logs/" + testId + "/karaf.log");
      System.setProperty("karaf.log", karafLog.getCanonicalPath());

      log.info("Starting Nexus[{}]", testId);
      launcher = launcherFactory.newInstance((Object) args);
      launcherClass.getMethod("launch").invoke(launcher);
    }
    finally {
      Thread.currentThread().setContextClassLoader(cl);
    }
  }

  @Override
  public void stopNexus() throws Exception {
    try {
      log.info("Stopping Nexus");

      if (launcher != null) {
        launcherClass.getMethod("destroy").invoke(launcher);
      }
      launcher = null;
    }
    catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof IllegalStateException) {
        log.debug("Ignoring", cause);
      }
      else {
        log.error("Stop failed", cause);
        throw Throwables.propagate(cause);
      }
    }
    finally {
      try {
        // The JVM caches URLs along with their current URL handler in a couple of static maps.
        // This causes unexpected issues when restarting legacy tests (even when using isolated
        // classloaders) because the cached handler persists across the restart and still refers
        // to the now shutdown framework. Felix has a few tricks to workaround this, but these
        // are defeated by the isolated legacy test classloader as the new framework cannot see
        // the old handler classes to reflectively update them.

        // (the other solution would be to not shutdown the framework when running legacy tests,
        // this would keep the old URL handlers alive at the cost of a few additional resources)

        Class<?> jarFileFactoryClass = Class.forName("sun.net.www.protocol.jar.JarFileFactory");
        Field fileCacheField = jarFileFactoryClass.getDeclaredField("fileCache");
        Field urlCacheField = jarFileFactoryClass.getDeclaredField("urlCache");
        fileCacheField.setAccessible(true);
        urlCacheField.setAccessible(true);
        ((Map<?, ?>) fileCacheField.get(null)).clear();
        ((Map<?, ?>) urlCacheField.get(null)).clear();
      } catch (Exception e) {
        log.warn("Unable to clear URL cache", e);
      }

      Thread.yield();
      System.gc();
    }
  }
}
TOP

Related Classes of org.sonatype.nexus.test.booter.EmbeddedNexusBooter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.