Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
magicprinc committed Mar 25, 2024
1 parent f248dea commit 01fd7f0
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ configurations.configureEach {
exclude group: 'io.vertx', module: 'vertx-ignite'

//!!! quick fix STEP 2: remove original smallrye-common-classloader and use fix instead
// exclude group: "io.smallrye.common", module: "smallrye-common-classloader"
exclude group: "io.smallrye.common", module: "smallrye-common-classloader"
}

dependencyManagement {
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/io/smallrye/common/classloader/ClassDefiner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.smallrye.common.classloader;

import java.lang.invoke.MethodHandles;
import java.security.AccessController;
import java.security.PrivilegedAction;

public class ClassDefiner {
public static Class<?> defineClass(MethodHandles.Lookup lookup, Class<?> parent, String className, byte[] classBytes) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(DefineClassPermission.getInstance());
}

return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
@Override
public Class<?> run() {
try {
MethodHandles.Lookup privateLookupIn = MethodHandles.privateLookupIn(parent, lookup);
return privateLookupIn.defineClass(classBytes);
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
}
}
});
}
}
239 changes: 239 additions & 0 deletions src/main/java/io/smallrye/common/classloader/ClassPathUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package io.smallrye.common.classloader;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.spi.FileSystemProvider;
import java.util.Enumeration;
import java.util.function.Consumer;
import java.util.function.Function;

public class ClassPathUtils {
private static final String FILE = "file";
private static final String JAR = "jar";

/**
* Invokes {@link #consumeAsStreams(ClassLoader, String, Consumer)} passing in
* an instance of the current thread's context classloader as the classloader
* from which to load the resources.
*
* @param resource resource path
* @param consumer resource input stream consumer
* @throws IOException in case of an IO failure
*/
public static void consumeAsStreams(String resource, Consumer<InputStream> consumer) throws IOException {
consumeAsStreams(Thread.currentThread().getContextClassLoader(), resource, consumer);
}

/**
* Locates all the occurrences of a resource on the classpath of the provided classloader
* and invokes the consumer providing the input streams for each located resource.
* The consumer does not have to close the provided input stream.
* This method was introduced to avoid calling {@link java.net.URL#openStream()} which
* in case the resource is found in an archive (such as JAR) locks the containing archive
* even if the caller properly closes the stream.
*
* @param cl classloader to load the resources from
* @param resource resource path
* @param consumer resource input stream consumer
* @throws IOException in case of an IO failure
*/
public static void consumeAsStreams(ClassLoader cl, String resource, Consumer<InputStream> consumer) throws IOException {
final Enumeration<URL> resources = cl.getResources(resource);
while (resources.hasMoreElements()) {
consumeStream(resources.nextElement(), consumer);
}
}

/**
* Invokes {@link #consumeAsPaths(ClassLoader, String, Consumer)} passing in
* an instance of the current thread's context classloader as the classloader
* from which to load the resources.
*
* @param resource resource path
* @param consumer resource path consumer
* @throws IOException in case of an IO failure
*/
public static void consumeAsPaths(String resource, Consumer<Path> consumer) throws IOException {
consumeAsPaths(Thread.currentThread().getContextClassLoader(), resource, consumer);
}

/**
* Locates specified resources on the classpath and attempts to represent them as local file system paths
* to be processed by a consumer. If a resource appears to be an actual file or a directory, it is simply
* passed to the consumer as-is. If a resource is an entry in a JAR, the entry will be resolved as an instance
* of {@link java.nio.file.Path} in a {@link java.nio.file.FileSystem} representing the JAR.
* If the protocol of the URL representing the resource is neither 'file' nor 'jar', the method will fail
* with an exception.
*
* @param cl classloader to load the resources from
* @param resource resource path
* @param consumer resource path consumer
* @throws IOException in case of an IO failure
*/
public static void consumeAsPaths(ClassLoader cl, String resource, Consumer<Path> consumer) throws IOException {
final Enumeration<URL> resources = cl.getResources(resource);
while (resources.hasMoreElements()) {
consumeAsPath(resources.nextElement(), consumer);
}
}

/**
* Attempts to represent a resource as a local file system path to be processed by a consumer.
* If a resource appears to be an actual file or a directory, it is simply passed to the consumer as-is.
* If a resource is an entry in a JAR, the entry will be resolved as an instance
* of {@link java.nio.file.Path} in a {@link java.nio.file.FileSystem} representing the JAR.
* If the protocol of the URL representing the resource is neither 'file' nor 'jar', the method will fail
* with an exception.
*
* @param url resource url
* @param consumer resource path consumer
*/
public static void consumeAsPath(URL url, Consumer<Path> consumer) {
processAsPath(url, p -> {
consumer.accept(p);
return null;
});
}

/**
* Attempts to represent a resource as a local file system path to be processed by a function.
* If a resource appears to be an actual file or a directory, it is simply passed to the function as-is.
* If a resource is an entry in a JAR, the entry will be resolved as an instance
* of {@link java.nio.file.Path} in a {@link java.nio.file.FileSystem} representing the JAR.
* If the protocol of the URL representing the resource is neither 'file' nor 'jar', the method will fail
* with an exception.
*
* @param url resource url
* @param function resource path function
*/
public static <R> R processAsPath(URL url, Function<Path, R> function) {
if (JAR.equals(url.getProtocol())) {
final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
try {
// We are loading "installed" FS providers that are loaded from the system classloader anyway
// To avoid potential ClassCastExceptions we are setting the context classloader to the system one
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
FileSystemProvider.installedProviders();
} finally {
Thread.currentThread().setContextClassLoader(ccl);
}

final String file = url.getFile();
final int exclam = file.indexOf('!');
try {
URL fileUrl;
Path jarPath;
String subPath;
if (exclam == -1) {
// assume the first element is a JAR file, not a plain file, since it was a `jar:` URL
fileUrl = new URL(file);
subPath = "/";
} else {
fileUrl = new URL(file.substring(0, exclam));
subPath = file.substring(exclam + 1);
}
if (!fileUrl.getProtocol().equals("file")) {
throw new IllegalArgumentException("Sub-URL of JAR URL is expected to have a scheme of `file`");
}
jarPath = toLocalPath(fileUrl);
return processAsJarPath(jarPath, subPath, function);
} catch (MalformedURLException e) {
throw new RuntimeException("Failed to create a URL for '" + file.substring(0, exclam) + "'", e);
}
}

if (FILE.equals(url.getProtocol())) {
return function.apply(toLocalPath(url));
}

throw new IllegalArgumentException("Unexpected protocol " + url.getProtocol() + " for URL " + url);
}

private static <R> R processAsJarPath(Path jarPath, String path, Function<Path, R> function) {
try (FileSystem jarFs = FileSystems.newFileSystem(jarPath, (ClassLoader) null)) {
Path localPath = jarFs.getPath("/");
int idx = path.indexOf('!');
if (idx == -1) {
return function.apply(localPath.resolve(path));
} else {
return processAsJarPath(localPath.resolve(path.substring(0, idx)), path.substring(idx + 1), function);
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read " + jarPath, e);
}
}

/**
* Invokes a consumer providing the input streams to read the content of the URL.
* The consumer does not have to close the provided input stream.
* This method was introduced to avoid calling {@link java.net.URL#openStream()} which
* in case the resource is found in an archive (such as JAR) locks the containing archive
* even if the caller properly closes the stream.
*
* @param url URL
* @param consumer input stream consumer
* @throws IOException in case of an IO failure
*/
public static void consumeStream(URL url, Consumer<InputStream> consumer) throws IOException {
readStream(url, is -> {
consumer.accept(is);
return null;
});
}

/**
* Invokes a function providing the input streams to read the content of the URL.
* The function does not have to close the provided input stream.
* This method was introduced to avoid calling {@link java.net.URL#openStream()} directly,
* which in case the resource is found in an archive (such as JAR) locks the containing archive
* even if the caller properly closes the stream.
*
* @param url URL
* @param function input stream processing function
* @throws IOException in case of an IO failure
*/
public static <R> R readStream(URL url, Function<InputStream, R> function) throws IOException {
if (JAR.equals(url.getProtocol())) {
URLConnection urlConnection = url.openConnection();
// prevent locking the jar after the inputstream is closed
urlConnection.setUseCaches(false);
try (InputStream is = urlConnection.getInputStream()) {
return function.apply(is);
}
}
if (FILE.equals(url.getProtocol())) {
try (InputStream is = Files.newInputStream(toLocalPath(url))) {
return function.apply(is);
}
}
try (InputStream is = url.openStream()) {
return function.apply(is);
}
}

/**
* Translates a URL to local file system path.
* In case the the URL couldn't be translated to a file system path,
* an instance of {@link IllegalArgumentException} will be thrown.
*
* @param url URL
* @return local file system path
*/
public static Path toLocalPath(final URL url) {
try {
return Paths.get(url.toURI());
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Failed to translate " + url + " to local path", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.smallrye.common.classloader;

import java.security.Permission;

public class DefineClassPermission extends Permission {
private static final long serialVersionUID = 142067672163413424L;
private static final DefineClassPermission INSTANCE = new DefineClassPermission();

public DefineClassPermission() {
super("");
}

public DefineClassPermission(final String name, final String actions) {
this();
}

public static DefineClassPermission getInstance() {
return INSTANCE;
}

@Override
public boolean implies(final Permission permission) {
return permission != null && permission.getClass() == this.getClass();
}

@Override
public boolean equals(final Object obj) {
return obj instanceof DefineClassPermission;
}

@Override
public int hashCode() {
return getClass().hashCode();
}

@Override
public String getActions() {
return "";
}
}

0 comments on commit 01fd7f0

Please sign in to comment.