Merge changes I93022cb7,I4b32d082

* changes:
  Rearrange code to avoid dependency on GerritClassLoader internally.
  Revert "Revert "Use custom gerrit class loader to avoid usage of reflection""
diff --git a/java/com/google/gerrit/common/BUILD b/java/com/google/gerrit/common/BUILD
index 1099919..8f930bb 100644
--- a/java/com/google/gerrit/common/BUILD
+++ b/java/com/google/gerrit/common/BUILD
@@ -23,6 +23,7 @@
         ":annotations",
         "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/extensions:api",
+        "//java/com/google/gerrit/launcher",
         "//java/com/google/gerrit/prettify:server",
         "//lib:guava",
         "//lib:jgit",
diff --git a/java/com/google/gerrit/common/IoUtil.java b/java/com/google/gerrit/common/IoUtil.java
index 09a8993..e51a6e5 100644
--- a/java/com/google/gerrit/common/IoUtil.java
+++ b/java/com/google/gerrit/common/IoUtil.java
@@ -14,20 +14,9 @@
 
 package com.google.gerrit.common;
 
-import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
 
 public final class IoUtil {
   public static void copyWithThread(InputStream src, OutputStream dst) {
@@ -64,50 +53,5 @@
     }.start();
   }
 
-  public static void loadJARs(Collection<Path> jars) {
-    if (jars.isEmpty()) {
-      return;
-    }
-
-    ClassLoader cl = IoUtil.class.getClassLoader();
-    if (!(cl instanceof URLClassLoader)) {
-      throw noAddURL("Not loaded by URLClassLoader", null);
-    }
-
-    @SuppressWarnings("resource") // Leave open so classes can be loaded.
-    URLClassLoader urlClassLoader = (URLClassLoader) cl;
-
-    Method addURL;
-    try {
-      addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
-      addURL.setAccessible(true);
-    } catch (SecurityException | NoSuchMethodException e) {
-      throw noAddURL("Method addURL not available", e);
-    }
-
-    Set<URL> have = Sets.newHashSet(Arrays.asList(urlClassLoader.getURLs()));
-    for (Path path : jars) {
-      try {
-        URL url = path.toUri().toURL();
-        if (have.add(url)) {
-          addURL.invoke(cl, url);
-        }
-      } catch (MalformedURLException | IllegalArgumentException | IllegalAccessException e) {
-        throw noAddURL("addURL " + path + " failed", e);
-      } catch (InvocationTargetException e) {
-        throw noAddURL("addURL " + path + " failed", e.getCause());
-      }
-    }
-  }
-
-  public static void loadJARs(Path jar) {
-    loadJARs(Collections.singleton(jar));
-  }
-
-  private static UnsupportedOperationException noAddURL(String m, Throwable why) {
-    String prefix = "Cannot extend classpath: ";
-    return new UnsupportedOperationException(prefix + m, why);
-  }
-
   private IoUtil() {}
 }
diff --git a/java/com/google/gerrit/common/JarUtil.java b/java/com/google/gerrit/common/JarUtil.java
new file mode 100644
index 0000000..b88a7ab
--- /dev/null
+++ b/java/com/google/gerrit/common/JarUtil.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// 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
+//
+// http://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.gerrit.common;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.launcher.GerritLauncher.GerritClassLoader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/** Provides util methods for dynamic loading jars */
+public final class JarUtil {
+  public static void loadJars(Collection<Path> jars) {
+    if (jars.isEmpty()) {
+      return;
+    }
+
+    ClassLoader cl = JarUtil.class.getClassLoader();
+    if (!(cl instanceof GerritClassLoader)) {
+      throw noAddURL("Not loaded by GerritClassLoader", null);
+    }
+
+    @SuppressWarnings("resource") // Leave open so classes can be loaded.
+    GerritClassLoader gerritClassLoader = (GerritClassLoader) cl;
+
+    Set<URL> have = Sets.newHashSet(Arrays.asList(gerritClassLoader.getURLs()));
+    for (Path path : jars) {
+      try {
+        URL url = path.toUri().toURL();
+        if (have.add(url)) {
+          gerritClassLoader.addURL(url);
+        }
+      } catch (MalformedURLException | IllegalArgumentException e) {
+        throw noAddURL("addURL " + path + " failed", e);
+      }
+    }
+  }
+
+  public static void loadJars(Path jar) {
+    loadJars(Collections.singleton(jar));
+  }
+
+  private static UnsupportedOperationException noAddURL(String m, Throwable why) {
+    String prefix = "Cannot extend classpath: ";
+    return new UnsupportedOperationException(prefix + m, why);
+  }
+
+  private JarUtil() {}
+}
diff --git a/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java b/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
index fa9b139..95df5be 100644
--- a/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
+++ b/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
@@ -35,7 +35,7 @@
   public static void loadSiteLib(Path libdir) {
     try {
       List<Path> jars = listJars(libdir);
-      IoUtil.loadJARs(jars);
+      JarUtil.loadJars(jars);
       logger.atFine().log("Loaded site libraries: %s", lazy(() -> jarList(jars)));
     } catch (IOException e) {
       logger.atSevere().withCause(e).log("Error scanning lib directory %s", libdir);
diff --git a/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java b/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java
index 594415a..bdc4f65 100644
--- a/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java
+++ b/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java
@@ -35,7 +35,7 @@
   SiteStaticDirectoryServlet(
       SitePaths site,
       @GerritServerConfig Config cfg,
-      @Named(StaticModule.CACHE) Cache<Path, Resource> cache) {
+      @Named(StaticModuleConstants.CACHE) Cache<Path, Resource> cache) {
     super(cache, cfg.getBoolean("site", "refreshHeaderFooter", true));
     Path p;
     try {
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 8319d9d..3c8287f 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.httpd.raw;
 
+import static com.google.gerrit.httpd.raw.StaticModuleConstants.CACHE;
+import static com.google.gerrit.httpd.raw.StaticModuleConstants.POLYGERRIT_INDEX_PATHS;
 import static java.nio.file.Files.exists;
 import static java.nio.file.Files.isReadable;
 
@@ -60,28 +62,6 @@
 public class StaticModule extends ServletModule {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  public static final String CACHE = "static_content";
-
-  /**
-   * Paths at which we should serve the main PolyGerrit application {@code index.html}.
-   *
-   * <p>Supports {@code "/*"} as a trailing wildcard.
-   */
-  public static final ImmutableList<String> POLYGERRIT_INDEX_PATHS =
-      ImmutableList.of(
-          "/",
-          "/c/*",
-          "/id/*",
-          "/p/*",
-          "/q/*",
-          "/x/*",
-          "/admin/*",
-          "/dashboard/*",
-          "/profile/*",
-          "/groups/self",
-          "/settings/*",
-          "/Documentation/q/*");
-
   /**
    * Paths that should be treated as static assets when serving PolyGerrit.
    *
diff --git a/java/com/google/gerrit/httpd/raw/StaticModuleConstants.java b/java/com/google/gerrit/httpd/raw/StaticModuleConstants.java
new file mode 100644
index 0000000..23cffa1
--- /dev/null
+++ b/java/com/google/gerrit/httpd/raw/StaticModuleConstants.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// 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
+//
+// http://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.gerrit.httpd.raw;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Various constants related to {@link StaticModule}
+ *
+ * <p>Methods of the {@link StaticModule} are not used internally in google, so moving public
+ * constants into the {@link StaticModuleConstants} allows to exclude {@link StaticModule} from the
+ * google-hosted gerrit hosts.
+ */
+public final class StaticModuleConstants {
+  public static final String CACHE = "static_content";
+
+  /**
+   * Paths at which we should serve the main PolyGerrit application {@code index.html}.
+   *
+   * <p>Supports {@code "/*"} as a trailing wildcard.
+   */
+  public static final ImmutableList<String> POLYGERRIT_INDEX_PATHS =
+      ImmutableList.of(
+          "/",
+          "/c/*",
+          "/id/*",
+          "/p/*",
+          "/q/*",
+          "/x/*",
+          "/admin/*",
+          "/dashboard/*",
+          "/profile/*",
+          "/groups/self",
+          "/settings/*",
+          "/Documentation/q/*");
+
+  private StaticModuleConstants() {};
+}
diff --git a/java/com/google/gerrit/launcher/GerritLauncher.java b/java/com/google/gerrit/launcher/GerritLauncher.java
index 8783593..07a071a 100644
--- a/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -60,6 +60,33 @@
   private static final String PKG = "com.google.gerrit.pgm";
   public static final String NOT_ARCHIVED = "NOT_ARCHIVED";
 
+  // Classloader that allows to add additional jars to the classpath.
+  public static class GerritClassLoader extends URLClassLoader {
+    static {
+      ClassLoader.registerAsParallelCapable();
+    }
+
+    /**
+     * Constructs a new URLClassLoader for the given URLs.
+     *
+     * @param urls the URLs from which to load classes and resources
+     * @param parent the parent class loader for delegation
+     */
+    GerritClassLoader(URL[] urls, ClassLoader parent) {
+      super(urls, parent);
+    }
+
+    /**
+     * Appends the additional URL to the list of URLs to search for classes and resources.
+     *
+     * @param url the URL to be added to the search path of URLs
+     */
+    @Override
+    public void addURL(URL url) {
+      super.addURL(url);
+    }
+  }
+
   private static ClassLoader daemonClassLoader;
 
   public static void main(String[] argv) throws Exception {
@@ -308,7 +335,7 @@
     if (!extapi.isEmpty()) {
       parent = URLClassLoader.newInstance(extapi.toArray(new URL[extapi.size()]), parent);
     }
-    return URLClassLoader.newInstance(jars.values().toArray(new URL[jars.size()]), parent);
+    return new GerritClassLoader(jars.values().toArray(new URL[jars.size()]), parent);
   }
 
   private static void extractJar(ZipFile zf, ZipEntry ze, NavigableMap<String, URL> jars)
diff --git a/java/com/google/gerrit/pgm/SwitchSecureStore.java b/java/com/google/gerrit/pgm/SwitchSecureStore.java
index 6dec2d8..063fcdb 100644
--- a/java/com/google/gerrit/pgm/SwitchSecureStore.java
+++ b/java/com/google/gerrit/pgm/SwitchSecureStore.java
@@ -18,7 +18,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.common.JarUtil;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.SiteLibraryLoaderUtil;
 import com.google.gerrit.pgm.util.SiteProgram;
@@ -79,7 +79,7 @@
       return -1;
     }
 
-    IoUtil.loadJARs(newSecureStorePath);
+    JarUtil.loadJars(newSecureStorePath);
     SiteLibraryLoaderUtil.loadSiteLib(sitePaths.lib_dir);
 
     logger.atInfo().log(
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index b59b924..abaefb2 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -21,7 +21,7 @@
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Die;
-import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.common.JarUtil;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.IndexType;
@@ -331,7 +331,7 @@
                 "%s has more that one implementation of %s interface",
                 secureStore, SecureStore.class.getName()));
       }
-      IoUtil.loadJARs(secureStoreLib);
+      JarUtil.loadJars(secureStoreLib);
       return new SecureStoreInitData(secureStoreLib, secureStores.get(0));
     } catch (IOException e) {
       throw new InvalidSecureStoreException(String.format("%s is not a valid jar", secureStore), e);