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

org.elasticsearch.plugins.UberModuleClassLoader Maven / Gradle / Ivy

There is a newer version: 8.14.0
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.plugins;

import org.elasticsearch.core.SuppressForbidden;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * This classloader will load classes from non-modularized sets of jars.
 * A synthetic module will be created for all jars in the bundle. We want
 * to be able to construct the read relationships in the module graph for this
 * synthetic module, which will make it different from the unnamed (classpath)
 * module.
 * 

* Internally, we can delegate to a URLClassLoader, which is battle-tested when * it comes to reading classes out of jars. *

* This classloader needs to avoid parent-first search: we'll check classes * against a list of packages in this synthetic module, and load a class * directly if it's part of this synthetic module. This will keep libraries from * clashing. */ public class UberModuleClassLoader extends SecureClassLoader implements AutoCloseable { private final Module module; private final URLClassLoader internalLoader; private final CodeSource codeSource; private final ModuleLayer.Controller moduleController; private final Set packageNames; static UberModuleClassLoader getInstance(ClassLoader parent, String moduleName, List jarPaths) { return getInstance(parent, moduleName, jarPaths, Set.of()); } @SuppressForbidden(reason = "need access to the jar file") @SuppressWarnings("removal") static UberModuleClassLoader getInstance(ClassLoader parent, String moduleName, List jarPaths, Set moduleDenyList) { ModuleFinder finder = ModuleSupport.ofSyntheticPluginModule(moduleName, jarPaths.toArray(new Path[0]), Set.of()); List jarURLs = new ArrayList<>(); try { for (Path jarPath : jarPaths) { URI toUri = jarPath.toUri(); URL toURL = toUri.toURL(); jarURLs.add(toURL); } } catch (IOException e) { throw new IllegalArgumentException(e); } ModuleLayer mparent = ModuleLayer.boot(); Configuration cf = mparent.configuration().resolve(finder, ModuleFinder.of(), Set.of(moduleName)); Set packageNames = new HashSet<>(); for (URL url : jarURLs) { try (JarFile jarFile = new JarFile(new File(url.toURI()))) { Set jarPackages = ModuleSupport.scan(jarFile) .classFiles() .stream() .map(e -> ModuleSupport.toPackageName(e, "/")) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toSet()); packageNames.addAll(jarPackages); } catch (IOException | URISyntaxException e) { throw new IllegalArgumentException(e); } } PrivilegedAction pa = () -> new UberModuleClassLoader( parent, moduleName, jarURLs.toArray(new URL[0]), cf, mparent, moduleDenyList, packageNames ); return AccessController.doPrivileged(pa); } /** * Constructor */ private UberModuleClassLoader( ClassLoader parent, String moduleName, URL[] jarURLs, Configuration cf, ModuleLayer mparent, Set moduleDenyList, Set packageNames ) { super(parent); this.internalLoader = new URLClassLoader(jarURLs); // code source is always the first jar on the list this.codeSource = new CodeSource(jarURLs[0], (CodeSigner[]) null); // Defining a module layer tells the Java virtual machine about the // classes that may be loaded from the module, and is what makes the // Class::getModule call return the name of our ubermodule. this.moduleController = ModuleLayer.defineModules(cf, List.of(mparent), s -> this); this.module = this.moduleController.layer().findModule(moduleName).orElseThrow(); // Every module reads java.base by default, but we can add all other modules // that are not in the deny list so that plugins can use, for example, java.management mparent.modules() .stream() .filter(Module::isNamed) .filter(m -> "java.base".equals(m.getName()) == false) .filter(m -> moduleDenyList.contains(m.getName()) == false) .forEach(m -> moduleController.addReads(module, m)); this.packageNames = packageNames; } /** * @param moduleName * The module name; or {@code null} to find the class in the * {@linkplain #getUnnamedModule() unnamed module} for this * class loader * @param name * The binary name of the class * * @return */ @Override protected Class findClass(String moduleName, String name) { if (Objects.isNull(moduleName) || this.module.getName().equals(moduleName) == false) { return null; } return findClass(name); } /** * @param name * The binary name of the class * * @return */ @Override protected Class findClass(String name) { String rn = name.replace('.', '/').concat(".class"); try (InputStream in = internalLoader.getResourceAsStream(rn)) { if (in == null) { return null; } byte[] bytes = in.readAllBytes(); return defineClass(name, bytes, 0, bytes.length, codeSource); } catch (IOException e) { throw new UncheckedIOException(e); } } /** * This classloader does not restrict access to resources in its jars. Users should * expect the same behavior as that provided by {@link URLClassLoader}. * * @param moduleName Name of this classloader's synthetic module * @param name The resource name * @return a URL for the resource, or null if the resource could not be found, * if the module name does not match, or if the loader is closed. */ @Override protected URL findResource(String moduleName, String name) { if (Objects.isNull(moduleName) || this.module.getName().equals(moduleName) == false) { return null; } return findResource(name); } /** * This classloader does not restrict access to resources in its jars. Users should * expect the same behavior as that provided by {@link URLClassLoader}. * * @param name The resource name * @return a URL for the resource, or null if the resource could not be found, * or if the loader is closed. */ @Override protected URL findResource(String name) { return internalLoader.findResource(name); } /** * This classloader does not restrict access to resources in its jars. Users should * expect the same behavior as that provided by {@link URLClassLoader}. * * @param name The resource name * @return an Enumeration of URLs. If the loader is closed, the Enumeration contains no elements. */ @Override protected Enumeration findResources(String name) throws IOException { return internalLoader.findResources(name); } @Override protected Class loadClass(String cn, boolean resolve) throws ClassNotFoundException { // if cn's package is in this ubermodule, look here only (or just first?) String packageName = packageName(cn); // TODO: do we need to check the security manager here? synchronized (getClassLoadingLock(cn)) { // check if already loaded Class c = findLoadedClass(cn); if (c == null) { if (this.packageNames.contains(packageName)) { // find in module or null c = findClass(cn); } else { c = getParent().loadClass(cn); } } if (c == null) { throw new ClassNotFoundException(cn); } if (resolve) { resolveClass(c); } return c; } } /** * Returns the package name for the given class name */ private String packageName(String cn) { int pos = cn.lastIndexOf('.'); return (pos < 0) ? "" : cn.substring(0, pos); } @Override @SuppressWarnings("removal") public void close() throws Exception { PrivilegedAction pa = () -> { try { internalLoader.close(); } catch (IOException e) { throw new IllegalStateException("Could not close internal URLClassLoader"); } return null; }; AccessController.doPrivileged(pa); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy