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

com.google.appengine.tools.info.SdkInfo Maven / Gradle / Ivy

There is a newer version: 2.0.27
Show 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.appengine.tools.info;

import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/** Retrieves installation information for the App Engine SDK. */
public class SdkInfo {

  public static final String SDK_ROOT_PROPERTY = "appengine.sdk.root";

  private static final String DEFAULT_SERVER = "appengine.google.com";

  // Relative path from SDK Root for the Jetty 9.4 Home lib directory.
  static final String JETTY9_HOME_LIB_PATH = "jetty94/jetty-home/lib";

  private static boolean isInitialized = false;
  private static File sdkRoot = null;
  private static List userLibFiles = null;
  private static List userLibs = null;
  private static SortedMap optionalUserLibsByName = null;
  private static SortedMap optionalToolsLibsByName = null;
  private static boolean isDevAppServerTest;

  private static final FileFilter NO_HIDDEN_FILES =
      new FileFilter() {
        @Override
        public boolean accept(File file) {
          return !file.isHidden();
        }
      };

  static List toURLs(List files) {
    List urls = new ArrayList(files.size());
    for (File file : files) {
      urls.add(toURL(file));
    }
    return urls;
  }

  static URL toURL(File file) {
    try {
      return file.toURI().toURL();
    } catch (MalformedURLException e) {
      throw new RuntimeException("Unable get a URL from " + file, e);
    }
  }

  static List getLibs(File sdkRoot, String libSubDir) {
    return getLibs(sdkRoot, libSubDir, false);
  }

  static List getLibsRecursive(File sdkRoot, String libSubDir) {
    return getLibs(sdkRoot, libSubDir, true);
  }

  private static List getLibs(File sdkRoot, String libSubDir, boolean recursive) {
    File subDir = new File(sdkRoot, "lib" + File.separator + libSubDir);

    if (!subDir.exists()) {
      throw new IllegalArgumentException("Unable to find " + subDir.getAbsolutePath());
    }

    List libs = new ArrayList();
    getLibs(subDir, libs, recursive);
    return libs;
  }

  private static void getLibs(File dir, List list, boolean recursive) {
    for (File f : listFiles(dir)) {
      if (f.isDirectory() && recursive) {
        getLibs(f, list, recursive);
      } else {
        if (f.getName().endsWith(".jar")) {
          list.add(f);
        }
      }
    }
  }

  private static File findSdkRoot() {
    String explicitRootString = System.getProperty(SDK_ROOT_PROPERTY);
    if (explicitRootString != null) {
      return new File(explicitRootString);
    }

    URL codeLocation = SdkInfo.class.getProtectionDomain().getCodeSource().getLocation();
    String msg =
        "Unable to discover the Google App Engine SDK root. This code should be loaded "
            + "from the SDK directory, but was instead loaded from "
            + codeLocation
            + ".  Specify "
            + "-Dappengine.sdk.root to override the SDK location.";
    File libDir;
    try {
      libDir = new File(codeLocation.toURI());
    } catch (URISyntaxException e) {
      libDir = new File(codeLocation.getFile());
    }
    while (!libDir.getName().equals("lib")) {
      libDir = libDir.getParentFile();
      if (libDir == null) {
        throw new RuntimeException(msg);
      }
    }
    return libDir.getParentFile();
  }

  /**
   * Returns the full paths of all shared libraries for the SDK. Users should compile against these
   * libraries, but not bundle them with their web application. These libraries are already
   * included as part of the App Engine runtime.
   */
  public static List getSharedLibs() {
    init();
    return Collections.unmodifiableList(toURLs(getSharedLibFiles()));
  }

  /** Returns the paths of all shared libraries for the SDK. */
  public static List getSharedLibFiles() {
    init();
    return determineSharedLibFiles();
  }

  /** @deprecated Use {@link #getOptionalUserLibs()} instead. */
  @Deprecated
  public static List getUserLibs() {
    init();
    return userLibs;
  }

  /** @deprecated Use {@link #getOptionalUserLibs()} instead. */
  @Deprecated
  public static List getUserLibFiles() {
    init();
    return userLibFiles;
  }

  /**
   * Returns all optional user libraries for the SDK. Users who opt to use these libraries should
   * both compile against and deploy them in the WEB-INF/lib folder of their web applications.
   */
  public static Collection getOptionalUserLibs() {
    init();
    return optionalUserLibsByName.values();
  }

  public static OptionalLib getOptionalUserLib(String name) {
    init();
    return optionalUserLibsByName.get(name);
  }

  /** Returns all optional tools libraries for the SDK. */
  public static Collection getOptionalToolsLibs() {
    init();
    return optionalToolsLibsByName.values();
  }

  public static OptionalLib getOptionalToolsLib(String name) {
    init();
    return optionalToolsLibsByName.get(name);
  }

  /** Returns the path to the root of the SDK. */
  public static File getSdkRoot() {
    init();
    return sdkRoot;
  }

  /**
   * Explicitly specifies the path to the root of the SDK. This takes precedence over the {@code
   * appengine.sdk.root} system property, but must be called before any other methods in this class.
   *
   * @throws IllegalStateException If any other methods have already been called.
   */
  public static synchronized void setSdkRoot(File root) {
    if (isInitialized && !sdkRoot.equals(root)) {
      throw new IllegalStateException("Cannot set SDK root after initialization has occurred.");
    }
    sdkRoot = root;
  }

  public static Version getLocalVersion() {
    return new LocalVersionFactory(getUserLibFiles()).getVersion();
  }

  public static String getDefaultServer() {
    return DEFAULT_SERVER;
  }

  /**
   * If {@code true}, the testing jar will be added to the shared libs. This is intended for use by
   * frameworks that want to run tests inside the isolated classloader.
   *
   * @param val Whether or the testing jar should be included on the shared path.
   */
  public static void includeTestingJarOnSharedPath(boolean val) {
    // This isn't pretty, but unless we want to move away from accessing all the
    // SDK info via static methods (which would break out tools customers),
    // this is the simplest way to adjust the jars that are considered shared
    // when we're running the dev appserver as part of a test.
    isDevAppServerTest = val;
  }

  private static synchronized void init() {
    if (!isInitialized) {
      if (sdkRoot == null) {
        sdkRoot = findSdkRoot();
      }
      if (new File(sdkRoot, "lib" + File.separator + "user").isDirectory()) {
        userLibFiles = Collections.unmodifiableList(getLibsRecursive(sdkRoot, "user"));
      } else {
        userLibFiles = Collections.emptyList();
      }
      userLibs = Collections.unmodifiableList(toURLs(userLibFiles));
      optionalUserLibsByName = Collections.unmodifiableSortedMap(determineOptionalUserLibs());
      optionalToolsLibsByName = Collections.unmodifiableSortedMap(determineOptionalToolsLibs());
      isInitialized = true;
    }
  }

  /**
   * Optional user libs reside under /lib/opt/user. Each top-level directory under this
   * path identifies an optional user library, and each sub-directory for a specific library
   * represents a version of that library. So for example we could have:
   * lib/opt/user/mylib1/v1/mylib.jar lib/opt/user/mylib1/v2/mylib.jar
   * lib/opt/user/mylib2/v1/mylib.jar lib/opt/user/mylib2/v2/mylib.jar
   *
   * @return A {@link SortedMap} from the name of the library to an {@link OptionalLib} that
   *     describes the library. The map is sorted by library name.
   */
  private static SortedMap determineOptionalUserLibs() {
    return determineOptionalLibs(new File(sdkRoot, "lib/opt/user"));
  }

  /**
   * Optional tools libs reside under /lib/opt/tools. Each top-level directory under this
   * path identifies an optional tools library, and each sub-directory for a specific library
   * represents a version of that library. So for example we could have:
   * lib/opt/tools/mylib1/v1/mylib.jar lib/opt/tools/mylib1/v2/mylib.jar
   * lib/opt/tools/mylib2/v1/mylib.jar lib/opt/tools/mylib2/v2/mylib.jar
   *
   * @return A {@link SortedMap} from the name of the library to an {@link OptionalLib} that
   *     describes the library. The map is sorted by library name.
   */
  private static SortedMap determineOptionalToolsLibs() {
    return determineOptionalLibs(new File(sdkRoot, "lib/opt/tools"));
  }

  private static SortedMap determineOptionalLibs(File root) {
    SortedMap map = new TreeMap();
    for (File libDir : listFiles(root)) {
      SortedMap> filesByVersion = new TreeMap>();
      for (File version : listFiles(libDir)) {
        List filesForVersion = new ArrayList();
        getLibs(version, filesForVersion, true);
        filesByVersion.put(version.getName(), filesForVersion);
      }
      // TODO: Read a description out of a README file.
      String description = "";
      OptionalLib userLib = new OptionalLib(libDir.getName(), description, filesByVersion);
      map.put(userLib.getName(), userLib);
    }
    return map;
  }

  private static List getJetty9Jars(String subDir) {
    File path = new File(sdkRoot, JETTY9_HOME_LIB_PATH + File.separator + subDir);

    if (!path.exists()) {
      throw new IllegalArgumentException("Unable to find " + path.getAbsolutePath());
    }
    List jars = new ArrayList<>();
    for (File f : SdkInfo.listFiles(path)) {
      if (f.getName().endsWith(".jar")) {
        // All but CDI jar. All the tests are still passing without CDI that should not be exposed
        // in our runtime (private Jetty dependency we do not want to expose to the customer).
        if (!(f.getName().startsWith("jetty-cdi") || f.getName().startsWith("cdi"))) {
          jars.add(f);
        }
      }
    }
    return jars;
  }

  static List getJetty9JspJars() {
    List lf = getJetty9Jars("apache-jsp");
    lf.addAll(getJetty9Jars("apache-jstl"));
    return lf;
  }

  static List getImplJars() {
    List lf = getJetty9Jars("");
    lf.addAll(getJetty9JspJars());
    // We also want the devserver to be able to handle annotated servlet, via ASM:
    lf.addAll(getJetty9Jars("annotations"));
    lf.addAll(getLibs(sdkRoot, "impl"));
    return Collections.unmodifiableList(lf);
  }

  static List getJetty9SharedLibFiles() {
    List sharedLibs;
    sharedLibs = new ArrayList<>();
    sharedLibs.add(new File(sdkRoot, "lib/shared/appengine-local-runtime-shared.jar"));
    File jettyHomeLib = new File(sdkRoot, JETTY9_HOME_LIB_PATH);

    sharedLibs.add(new File(jettyHomeLib, "servlet-api-3.1.jar"));
    File schemas = new File(jettyHomeLib, "servlet-schemas-3.1.jar");
    if (schemas.exists()) {
      sharedLibs.add(schemas);
    } else {
      schemas = new File(jettyHomeLib, "jetty-schemas-3.1.jar");
      if (schemas.exists()) {
        sharedLibs.add(schemas);
      }
    }

    // We want to match this file: "jetty-util-9.3.8.v20160314.jar"
    // but without hardcoding the Jetty version which is changing from time to time.
    class JettyVersionFilter implements FileFilter {
      @Override
      public boolean accept(File file) {
        return file.getName().startsWith("jetty-util-");
      }
    }
    File[] files = jettyHomeLib.listFiles(new JettyVersionFilter());
    sharedLibs.addAll(Arrays.asList(files));
    sharedLibs.addAll(getJetty9JspJars());
    return sharedLibs;
  }

  private static List determineSharedLibFiles() {
    List sharedLibs = getJetty9SharedLibFiles();

    if (isDevAppServerTest) {
      // If we're running the dev appserver as part of a test, add the testing
      // jar to the shared classpath.  This will allow things like
      // ApiProxyLocalImpl to be on the application classpath (necessary
      // because the application classpath includes the test, and the test
      // uses LocalServiceTestHelper, which interacts directly with
      // ApiProxyLocalImpl) but to make privileged calls like accessing the
      // service loader.
      sharedLibs.addAll(getLibsRecursive(sdkRoot, "testing"));
    }
    return Collections.unmodifiableList(sharedLibs);
  }

  /**
   * A version of {@link File#listFiles()} that never returns {@code null}. Historically this has
   * been an issue, since listFiles() can return null if the parent directory does not exist or is
   * not readable.
   *
   * @param dir The directory whose files we want to list.
   * @return The contents of the provided directory.
   */
  static File[] listFiles(File dir) {
    // Some customers check the sdk into subversion, which puts hidden
    // directories into each sdk directory. We don't want these directories in
    // our list.
    File[] files = dir.listFiles(NO_HIDDEN_FILES);
    if (files == null) {
      return new File[0];
    }
    return files;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy