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

com.google.apphosting.runtime.ClassPathUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021 Google LLC
 *
 * Licensed 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
 *
 *     https://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.
 */

package com.google.apphosting.runtime;

import static java.util.stream.Collectors.joining;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
 * {@code ClassPathUtils} provides utility functions that are useful in dealing with class paths.
 *
 */
public class ClassPathUtils {
  // Note: we should not depend on Guava or Flogger in the small bootstap Main.
  private static final Logger logger = Logger.getLogger(ClassPathUtils.class.getName());

  private static final String RUNTIME_BASE_PROPERTY = "classpath.runtimebase";
  private static final String RUNTIME_IMPL_PROPERTY = "classpath.runtime-impl";
  private static final String RUNTIME_SHARED_PROPERTY = "classpath.runtime-shared";
  private static final String PREBUNDLED_PROPERTY = "classpath.prebundled";
  private static final String API_PROPERTY = "classpath.api-map";
  private static final String CONNECTOR_J_PROPERTY = "classpath.connector-j";
  private static final String APPENGINE_API_LEGACY_PROPERTY = "classpath.appengine-api-legacy";
  private static final String LEGACY_PROPERTY = "classpath.legacy";
  // Cannot use Guava library in this classloader.
  private static final String PATH_SEPARATOR = System.getProperty("path.separator");

  private final File root;
  private File frozenApiJarFile;

  public ClassPathUtils() {
    this(null);
  }

  public ClassPathUtils(File root) {

    String runtimeBase = System.getProperty(RUNTIME_BASE_PROPERTY);
    if (runtimeBase == null) {
      throw new RuntimeException("System property not defined: " + RUNTIME_BASE_PROPERTY);
    }
    this.root = root;

    if (!new File(runtimeBase, "java_runtime_launcher").exists()) {
      initForJava11OrAbove(runtimeBase);
      return;
    }

    String profilerJar = null;
    if (System.getenv("GAE_PROFILER_MODE") != null) {
      profilerJar = "profiler.jar"; // Close source, not in Maven.;
      logger.log(Level.INFO, "AppEngine profiler enabled.");
    }
    List runtimeClasspathEntries =
        Arrays.asList("jars/runtime-impl-jetty9.jar", profilerJar);

    String runtimeClasspath =
        runtimeClasspathEntries.stream()
            .filter(t -> t != null)
            .map(s -> runtimeBase + "/" + s)
            .collect(joining(PATH_SEPARATOR));

    if (System.getProperty(RUNTIME_IMPL_PROPERTY) != null) {
      // Prepend existing value, only used in our tests.
      runtimeClasspath =
          System.getProperty(RUNTIME_IMPL_PROPERTY) + PATH_SEPARATOR + runtimeClasspath;
    }
    // Keep old properties for absolute compatibility if ever some public apps depend on them:
    System.setProperty(RUNTIME_IMPL_PROPERTY, runtimeClasspath);
    logger.log(Level.INFO, "Using runtime classpath: " + runtimeClasspath);

    // The frozen API jar we must use for ancient customers still relying on the obsolete feature
    // that when deploying with api_version: 1.0 in generated app.yaml
    // we need to add our own legacy jar.
    frozenApiJarFile = new File(new File(root, runtimeBase), "/appengine-api.jar");
    System.setProperty(RUNTIME_SHARED_PROPERTY, runtimeBase + "/jars/runtime-shared-jetty9.jar");
    System.setProperty(API_PROPERTY, "1.0=" + runtimeBase + "/jars/appengine-api-1.0-sdk.jar");
    System.setProperty(
        APPENGINE_API_LEGACY_PROPERTY, runtimeBase + "/jars/appengine-api-legacy.jar");
    System.setProperty(CONNECTOR_J_PROPERTY, runtimeBase + "/jdbc-mysql-connector.jar");
    System.setProperty(PREBUNDLED_PROPERTY, runtimeBase + "/conscrypt.jar");
    System.setProperty(LEGACY_PROPERTY, runtimeBase + "/legacy.jar");
  }

  private void initForJava11OrAbove(String runtimeBase) {
    // No native launcher means gen2 java11 or java17 or java21, not java8.
    /*
        New content is very simple now (from maven jars):
        ls blaze-bin/java/com/google/apphosting/runtime_java11/deployment_java11
        runtime-impl-jetty9.jar for Jetty9
        runtime-impl-jetty12.jar for EE8 and EE10
        runtime-main.jar shared bootstrap main
        runtime-shared.jar (for Jetty9)
        runtime-shared-jetty12.jar for EE8
        runtime-shared-jetty12-ee10.jar for EE10
    */
      List runtimeClasspathEntries
              = Boolean.getBoolean("appengine.use.EE8") || Boolean.getBoolean("appengine.use.EE10")
              ? Arrays.asList("runtime-impl-jetty12.jar")
              : Arrays.asList("runtime-impl-jetty9.jar");

    String runtimeClasspath =
        runtimeClasspathEntries.stream()
            .filter(t -> t != null)
            .map(s -> runtimeBase + "/" + s)
            .collect(joining(PATH_SEPARATOR));

    if (System.getProperty(RUNTIME_IMPL_PROPERTY) != null) {
      // Prepend existing value, only used in our tests.
      runtimeClasspath =
          System.getProperty(RUNTIME_IMPL_PROPERTY) + PATH_SEPARATOR + runtimeClasspath;
    }

    // Keep old properties for absolute compatibility if ever some public apps depend on them:
    System.setProperty(RUNTIME_IMPL_PROPERTY, runtimeClasspath);
    logger.log(Level.INFO, "Using runtime classpath: " + runtimeClasspath);

    if (Boolean.getBoolean("appengine.use.EE10")) {
      logger.log(Level.INFO, "AppEngine is using EE10 profile.");
      System.setProperty(RUNTIME_SHARED_PROPERTY, runtimeBase + "/runtime-shared-jetty12-ee10.jar");
    } else if (Boolean.getBoolean("appengine.use.EE8")) {
      logger.log(Level.INFO, "AppEngine is using EE8 profile.");
      System.setProperty(RUNTIME_SHARED_PROPERTY, runtimeBase + "/runtime-shared-jetty12.jar");
    } else {
      System.setProperty(RUNTIME_SHARED_PROPERTY, runtimeBase + "/runtime-shared-jetty9.jar");
    }

    frozenApiJarFile = new File(runtimeBase, "/appengine-api-1.0-sdk.jar");
  }

  public URL[] getRuntimeImplUrls() {
    return parseClasspath(System.getProperty(RUNTIME_IMPL_PROPERTY));
  }

  public URL[] getRuntimeSharedUrls() {
    return parseClasspath(System.getProperty(RUNTIME_SHARED_PROPERTY));
  }

  public URL[] getPrebundledUrls() {
    String path = System.getProperty(PREBUNDLED_PROPERTY);
    if (path == null) {
      return new URL[0];
    } else {
      return parseClasspath(path);
    }
  }

  public URL[] getConnectorJUrls() {
    String path = System.getProperty(CONNECTOR_J_PROPERTY);
    if (path == null) {
      return new URL[0];
    } else {
      return parseClasspath(path);
    }
  }

  /**
   * Returns the URLs for legacy jars. This may be empty or it may be one or more jars that contain
   * classes like {@code com.google.appengine.repackaged.org.joda.Instant}, the old form of
   * repackaging. We've switched to classes like {@code
   * com.google.appengine.repackaged.org.joda.$Instant}, with a {@code $}, but this jar can
   * optionally be added to an app's classpath if it is referencing the old names. Other legacy
   * classes, unrelated to repackaging, may also appear in these jars.
   */
  public URL[] getLegacyJarUrls() {
    String path = System.getProperty(LEGACY_PROPERTY);
    if (path == null) {
      return new URL[0];
    } else {
      return parseClasspath(path);
    }
  }

  /**
   * Returns a {@link File} for the frozen old API jar,
   */
  public File getFrozenApiJar() {
    return frozenApiJarFile;
  }

  @Nullable
  public File getAppengineApiLegacyJar() {
    String filename = System.getProperty(APPENGINE_API_LEGACY_PROPERTY);
    return filename == null ? null : new File(root, filename);
  }

  /**
   * Parse the specified string into individual files (using the machine's path separator) and
   * return an array containing a {@link URL} object representing each file.
   */
  public URL[] parseClasspath(String classpath) {
    List urls = new ArrayList();

    StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator);
    while (tokenizer.hasMoreTokens()) {
      String token = tokenizer.nextToken();
      try {
        // Avoid File.toURI() and File.toURL() here as they do an
        // unnecessary stat call.
        urls.add(new URL("file", "", new File(root, token).getAbsolutePath().replace('\\', '/')));
      } catch (MalformedURLException ex) {
        logger.log(Level.WARNING, "Could not parse " + token + " as a URL, ignoring.", ex);
      }
    }

    return urls.toArray(new URL[0]);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy