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

com.google.apphosting.utils.config.ClassPathBuilder Maven / Gradle / Ivy

There is a newer version: 2.0.31
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.apphosting.utils.config;

import com.google.apphosting.utils.config.AppEngineWebXml.ClassLoaderConfig;
import com.google.apphosting.utils.config.AppEngineWebXml.PrioritySpecifierEntry;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Applies class loader configuration rules to class path {@link URL URLs}.
 */
public class ClassPathBuilder {
  // classes directory URLs end in "/classes" and optionally a terminating "/"
  private static final Pattern CLASSES_REGEX = Pattern.compile(".*/classes/?");

  // Matches "appengine-api.jar" and paths like appengine-api-1.0-sdk-1.7.0.jar
  private static final Pattern APPENGINE_API_REGEX =
      Pattern.compile(".*/appengine-api(-?[0-9\\.]*-sdk-[0-9\\.]*)?\\.jar");

  // Pairs a URL and priority for sorting.
  private static class UrlPriority {
    final URL url;
    final double priority;

    UrlPriority(URL url, double priority) {
      this.url = url;
      this.priority = priority;
    }

    // NOTE: Equality and hash code is *only* sensitive to url because
    // we don't want the urls set to have duplicates of the url.
    // Generated by eclipse.
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((url == null) ? 0 : url.hashCode());
      return result;
    }

    // Generated by eclipse.
    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof UrlPriority)) {
        return false;
      }
      UrlPriority other = (UrlPriority) obj;
      return Objects.equals(url, other.url);
    }
  }

  // Comparator for sorting UrlPriority objects. Higher priorities come first.
  private static final Comparator URL_PRIORITY_COMP =
      Comparator.comparingDouble(u -> u.priority).reversed();

  // The set of added URL/priority pairs.
  private final Set urls = new LinkedHashSet();

  // The priority specifiers.
  private final List priorityEntries;

  // For detecting unused priority specifiers.
  private final boolean[] usedPrioritySpecifiers;

  private URL[] sortedUrls = null;

  /**
   * @param classLoaderConfig The class loader config, may be null.
   */
  public ClassPathBuilder(ClassLoaderConfig classLoaderConfig) {
    if (classLoaderConfig == null) {
      priorityEntries = ImmutableList.of();
    } else {
      priorityEntries = classLoaderConfig.getEntries();
    }
    usedPrioritySpecifiers = new boolean[priorityEntries.size()];
  }

  // Add a URL based on priority.
  private void addUrl(URL url, double defaultPriority) {
    if (sortedUrls != null) {
      throw new IllegalStateException("add* calls are not allowed after getUrls() has been called");
    }
    Double priority = findPriority(url);
    urls.add(new UrlPriority(url, null == priority ? defaultPriority : priority));
  }

  // Return the designated priority for a given URL based on the priorityEntries.
  private Double findPriority(URL url) {
    // Get the filename part of the url.
    String fileName = new File(url.getPath()).getName();

    for (int i = 0; i < usedPrioritySpecifiers.length; ++i) {
      if (priorityEntries.get(i).getFilename().equals(fileName)) {
        usedPrioritySpecifiers[i] = true;
        return priorityEntries.get(i).getPriorityValue();
      }
    }
    return null;
  }

  /**
   * Add the classes {@link URL URLs}.
   */
  public void addClassesUrl(URL url) {
    addUrl(url, 100.0d);
  }

  /**
   * Add the appengine-api.jar {@link URL URLs}.
   */
  public void addAppengineJar(URL url) {
    addUrl(url, 0.5d);
  }

  /**
   * Add application specific jar {@link URL URLs}.
   */
  public void addAppJar(URL url) {
    addUrl(url, 0.0d);
  }

  /**
   * Returns the class loader urls in the order modified by the priority
   * specifiers in the {@link ClassLoaderConfig} passed to the constructor.
   */
  public URL[] getUrls() {
    if (sortedUrls == null) {
      UrlPriority[] classPath = urls.toArray(new UrlPriority[urls.size()]);
      // This is a stable sort.  It should not change the order in which the
      // URLs were added.
      Arrays.sort(classPath, URL_PRIORITY_COMP);

      sortedUrls = new URL[classPath.length];
      for (int i = 0; i < classPath.length; ++i) {
        sortedUrls[i] = classPath[i].url;
      }
    }
    return sortedUrls;
  }

  /**
   * Returns a log message if there were unused specifiers or an empty string.
   * 

Must be called after calling {@link #getUrls()}. */ public String getLogMessage() { if (sortedUrls == null) { throw new IllegalStateException( "Cannot call getLogMessage() without first calling getUrls()"); } StringBuilder builder = new StringBuilder(); for (int i = 0; i < usedPrioritySpecifiers.length; ++i) { if (!usedPrioritySpecifiers[i]) { builder.append("priority-specifier: filename: "); builder.append(priorityEntries.get(i).getFilename()); if (priorityEntries.get(i).getPriority() != null) { builder.append(" priority: "); builder.append(priorityEntries.get(i).getPriorityValue()); } } } String errors = builder.toString(); if (!errors.isEmpty()) { return "appengine-web.xml contains unused class-loader-config priority-specifier values.\n" + "unused values: " + errors + "\nresulting classpath: " + Arrays.toString(sortedUrls) + "\nTo remove this warning, remove the unused priority-specifier values " + "from appengine-web.xml or add a file matching the unused priority-specifier values."; } return ""; } /** * Scans through a collection of URLs for various patterns and adds them * with the correct priority. */ public void addUrls(Collection urls) { for (URL url : urls) { if (CLASSES_REGEX.matcher(url.getPath()).matches()) { addClassesUrl(url); } else if (APPENGINE_API_REGEX.matcher(url.getPath()).matches()) { addAppengineJar(url); } else { addAppJar(url); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy