/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.javaagent;

import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.bootstrap.JavaagentFileHolder;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

/**
 * Premain-Class for the OpenTelemetry Java agent.
 *
 * <p>The bootstrap process of the agent is somewhat complicated and care has to be taken to make
 * sure things do not get broken by accident.
 *
 * <p>JVM loads this class onto app's class loader, afterwards agent needs to inject its classes
 * onto bootstrap classpath. This leads to this class being visible on bootstrap. This in turn means
 * that this class may be loaded again on bootstrap by accident if we ever reference it after
 * bootstrap has been setup.
 *
 * <p>In order to avoid this we need to make sure we do a few things:
 *
 * <ul>
 *   <li>Do as little as possible here
 *   <li>Never reference this class after we have setup bootstrap and jumped over to 'real' agent
 *       code
 *   <li>Do not store any static data in this class
 *   <li>Do dot touch any logging facilities here so we can configure them later
 * </ul>
 */
// Too early for logging
@SuppressWarnings("SystemOut")
public final class OpenTelemetryAgent {

  public static void premain(String agentArgs, Instrumentation inst) {
    startAgent(inst, true);
  }

  public static void agentmain(String agentArgs, Instrumentation inst) {
    startAgent(inst, false);
  }

  private static void startAgent(Instrumentation inst, boolean fromPremain) {
    try {
      File javaagentFile = installBootstrapJar(inst);
      InstrumentationHolder.setInstrumentation(inst);
      JavaagentFileHolder.setJavaagentFile(javaagentFile);
      AgentInitializer.initialize(inst, javaagentFile, fromPremain);
    } catch (Throwable ex) {
      // Don't rethrow.  We don't have a log manager here, so just print.
      System.err.println("ERROR " + OpenTelemetryAgent.class.getName());
      ex.printStackTrace();
    }
  }

  private static synchronized File installBootstrapJar(Instrumentation inst)
      throws IOException, URISyntaxException {
    // we are not using OpenTelemetryAgent.class.getProtectionDomain().getCodeSource() to get agent
    // location because getProtectionDomain does a permission check with security manager
    ClassLoader classLoader = OpenTelemetryAgent.class.getClassLoader();
    if (classLoader == null) {
      classLoader = ClassLoader.getSystemClassLoader();
    }
    URL url =
        classLoader.getResource(OpenTelemetryAgent.class.getName().replace('.', '/') + ".class");
    if (url == null || !"jar".equals(url.getProtocol())) {
      throw new IllegalStateException("could not get agent jar location from url " + url);
    }
    String resourcePath = url.toURI().getSchemeSpecificPart();
    int protocolSeparatorIndex = resourcePath.indexOf(":");
    int resourceSeparatorIndex = resourcePath.indexOf("!/");
    if (protocolSeparatorIndex == -1 || resourceSeparatorIndex == -1) {
      throw new IllegalStateException("could not get agent location from url " + url);
    }
    String agentPath = resourcePath.substring(protocolSeparatorIndex + 1, resourceSeparatorIndex);
    File javaagentFile = new File(agentPath);

    if (!javaagentFile.isFile()) {
      throw new IllegalStateException(
          "agent jar location doesn't appear to be a file: " + javaagentFile.getAbsolutePath());
    }

    // verification is very slow before the JIT compiler starts up, which on Java 8 is not until
    // after premain execution completes
    JarFile agentJar = new JarFile(javaagentFile, false);
    verifyJarManifestMainClassIsThis(javaagentFile, agentJar);
    inst.appendToBootstrapClassLoaderSearch(agentJar);
    return javaagentFile;
  }

  // this protects against the case where someone adds the contents of opentelemetry-javaagent.jar
  // by mistake to their application's "uber.jar"
  //
  // the reason this can cause issues is because we locate the agent jar based on the CodeSource of
  // the OpenTelemetryAgent class, and then we add that jar file to the bootstrap class path
  //
  // but if we find the OpenTelemetryAgent class in an uber jar file, and we add that (whole) uber
  // jar file to the bootstrap class loader, that can cause some applications to break, as there's a
  // lot of application and library code that doesn't handle getClassLoader() returning null
  // (e.g. https://github.com/qos-ch/logback/pull/291)
  private static void verifyJarManifestMainClassIsThis(File jarFile, JarFile agentJar)
      throws IOException {
    Manifest manifest = agentJar.getManifest();
    if (manifest.getMainAttributes().getValue("Premain-Class") == null) {
      throw new IllegalStateException(
          "The agent was not installed, because the agent was found in '"
              + jarFile
              + "', which doesn't contain a Premain-Class manifest attribute. Make sure that you"
              + " haven't included the agent jar file inside of an application uber jar.");
    }
  }

  /**
   * Main entry point.
   *
   * @param args command line arguments
   */
  public static void main(String... args) {
    try {
      System.out.println(OpenTelemetryAgent.class.getPackage().getImplementationVersion());
    } catch (RuntimeException e) {
      System.out.println("Failed to parse agent version");
      e.printStackTrace();
    }
  }

  private OpenTelemetryAgent() {}
}
