-
Notifications
You must be signed in to change notification settings - Fork 3.5k
remove dependency on org.reflections #18295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||||||
| package org.logstash.plugins.discovery; | ||||||||||
|
|
||||||||||
| import java.io.IOException; | ||||||||||
| import java.io.UncheckedIOException; | ||||||||||
| import java.lang.annotation.Annotation; | ||||||||||
| import java.net.JarURLConnection; | ||||||||||
| import java.net.URISyntaxException; | ||||||||||
| import java.net.URL; | ||||||||||
| import java.nio.file.Files; | ||||||||||
| import java.nio.file.Path; | ||||||||||
| import java.nio.file.Paths; | ||||||||||
| import java.util.Enumeration; | ||||||||||
| import java.util.HashSet; | ||||||||||
| import java.util.Set; | ||||||||||
| import java.util.jar.JarEntry; | ||||||||||
| import java.util.jar.JarFile; | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Minimal package scanner to locate classes within Logstash's own packages. | ||||||||||
| */ | ||||||||||
| final class PackageScanner { | ||||||||||
|
|
||||||||||
| private PackageScanner() { | ||||||||||
| } | ||||||||||
|
|
||||||||||
| static Set<Class<?>> scanForAnnotation(String basePackage, Class<? extends Annotation> annotation, ClassLoader loader) { | ||||||||||
| Set<String> classNames = collectClassNames(basePackage, loader); | ||||||||||
| Set<Class<?>> result = new HashSet<>(); | ||||||||||
| for (String className : classNames) { | ||||||||||
| if (className.contains("$")) { | ||||||||||
| continue; | ||||||||||
| } | ||||||||||
| try { | ||||||||||
| Class<?> candidate = Class.forName(className, false, loader); | ||||||||||
| if (candidate.isAnnotationPresent(annotation)) { | ||||||||||
| result.add(candidate); | ||||||||||
| } | ||||||||||
| } catch (ClassNotFoundException | LinkageError e) { | ||||||||||
| throw new IllegalStateException("Unable to load class discovered during scanning: " + className, e); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| return result; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private static Set<String> collectClassNames(String basePackage, ClassLoader loader) { | ||||||||||
| String resourcePath = basePackage.replace('.', '/'); | ||||||||||
| Set<String> classNames = new HashSet<>(); | ||||||||||
| try { | ||||||||||
| Enumeration<URL> resources = loader.getResources(resourcePath); | ||||||||||
| while (resources.hasMoreElements()) { | ||||||||||
| URL resource = resources.nextElement(); | ||||||||||
| String protocol = resource.getProtocol(); | ||||||||||
| if ("file".equals(protocol)) { | ||||||||||
| scanDirectory(resource, basePackage, classNames); | ||||||||||
| } else if ("jar".equals(protocol) || resource.toString().contains("!")) { | ||||||||||
| scanJar(resource, resourcePath, classNames); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } catch (IOException e) { | ||||||||||
| throw new UncheckedIOException("Failed to scan package: " + basePackage, e); | ||||||||||
| } | ||||||||||
| return classNames; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private static void scanDirectory(URL resource, String basePackage, Set<String> classNames) { | ||||||||||
| try { | ||||||||||
| Path directory = Paths.get(resource.toURI()); | ||||||||||
| if (!Files.exists(directory)) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| Files.walk(directory) | ||||||||||
| .filter(Files::isRegularFile) | ||||||||||
| .filter(path -> path.getFileName().toString().endsWith(".class")) | ||||||||||
| .forEach(path -> { | ||||||||||
| Path relative = directory.relativize(path); | ||||||||||
| String className = basePackage + '.' + relative.toString() | ||||||||||
| .replace('/', '.').replace('\\', '.'); | ||||||||||
| className = className.substring(0, className.length() - ".class".length()); | ||||||||||
| classNames.add(className); | ||||||||||
| }); | ||||||||||
| } catch (IOException | URISyntaxException e) { | ||||||||||
| throw new IllegalStateException("Failed to scan directory for classes: " + resource, e); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private static void scanJar(URL resource, String resourcePath, Set<String> classNames) { | ||||||||||
| try { | ||||||||||
| JarURLConnection connection = (JarURLConnection) resource.openConnection(); | ||||||||||
|
||||||||||
| JarURLConnection connection = (JarURLConnection) resource.openConnection(); | |
| JarURLConnection connection = (JarURLConnection) resource.openConnection(); | |
| // Disable caching to prevent file locking issues (especially on Windows) and to ensure | |
| // that the JAR file can be closed and deleted after use. |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,12 +31,9 @@ | |||||||
| import co.elastic.logstash.api.LogstashPlugin; | ||||||||
| import co.elastic.logstash.api.Output; | ||||||||
| import org.logstash.plugins.PluginLookup.PluginType; | ||||||||
| import org.reflections.Reflections; | ||||||||
| import org.reflections.util.ClasspathHelper; | ||||||||
| import org.reflections.util.ConfigurationBuilder; | ||||||||
|
|
||||||||
| import java.lang.annotation.Annotation; | ||||||||
| import java.lang.reflect.Constructor; | ||||||||
| import java.lang.reflect.Modifier; | ||||||||
| import java.util.HashMap; | ||||||||
| import java.util.Map; | ||||||||
| import java.util.Optional; | ||||||||
|
|
@@ -47,7 +44,7 @@ | |||||||
| * This is singleton ofr two reasons: | ||||||||
| * <ul> | ||||||||
| * <li>it's a registry so no need for multiple instances</li> | ||||||||
| * <li>the Reflections library used need to run in single thread during discovery phase</li> | ||||||||
| * <li>plugin discovery touches the classpath, so we keep it single-threaded</li> | ||||||||
| * </ul> | ||||||||
| * */ | ||||||||
| public final class PluginRegistry { | ||||||||
|
|
@@ -79,34 +76,30 @@ public static PluginRegistry getInstance() { | |||||||
|
|
||||||||
| @SuppressWarnings("unchecked") | ||||||||
| private void discoverPlugins() { | ||||||||
| // the constructor of Reflection must be called only by one thread, else there is a | ||||||||
| // risk that the first thread that completes close the Zip files for the others. | ||||||||
| // scan all .class present in package classpath | ||||||||
| final ConfigurationBuilder configurationBuilder = new ConfigurationBuilder() | ||||||||
| .setUrls(ClasspathHelper.forPackage("org.logstash.plugins")) | ||||||||
| .filterInputsBy(input -> input.endsWith(".class")); | ||||||||
| Reflections reflections = new Reflections(configurationBuilder); | ||||||||
|
|
||||||||
| Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(LogstashPlugin.class); | ||||||||
| // the discovery runs single-threaded to avoid concurrent access issues while scanning | ||||||||
| Set<Class<?>> annotated = PackageScanner.scanForAnnotation("org.logstash.plugins", LogstashPlugin.class, PluginRegistry.class.getClassLoader()); | ||||||||
| for (final Class<?> cls : annotated) { | ||||||||
| for (final Annotation annotation : cls.getAnnotations()) { | ||||||||
| if (annotation instanceof LogstashPlugin) { | ||||||||
| String name = ((LogstashPlugin) annotation).name(); | ||||||||
| if (Filter.class.isAssignableFrom(cls)) { | ||||||||
| filters.put(name, (Class<Filter>) cls); | ||||||||
| } | ||||||||
| if (Output.class.isAssignableFrom(cls)) { | ||||||||
| outputs.put(name, (Class<Output>) cls); | ||||||||
| } | ||||||||
| if (Input.class.isAssignableFrom(cls)) { | ||||||||
| inputs.put(name, (Class<Input>) cls); | ||||||||
| } | ||||||||
| if (Codec.class.isAssignableFrom(cls)) { | ||||||||
| codecs.put(name, (Class<Codec>) cls); | ||||||||
| } | ||||||||
|
|
||||||||
| break; | ||||||||
| } | ||||||||
| if (Modifier.isAbstract(cls.getModifiers())) { | ||||||||
| continue; | ||||||||
| } | ||||||||
|
|
||||||||
| LogstashPlugin annotation = cls.getAnnotation(LogstashPlugin.class); | ||||||||
| if (annotation == null) { | ||||||||
| continue; | ||||||||
| } | ||||||||
|
Comment on lines
+87
to
+89
|
||||||||
| if (annotation == null) { | |
| continue; | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package org.logstash.plugins.discovery; | ||
|
|
||
| import co.elastic.logstash.api.Codec; | ||
| import co.elastic.logstash.api.Filter; | ||
| import co.elastic.logstash.api.Input; | ||
| import co.elastic.logstash.api.Output; | ||
| import org.junit.Test; | ||
|
|
||
| import static org.junit.Assert.assertNotNull; | ||
| import static org.junit.Assert.assertTrue; | ||
|
|
||
| public class PluginRegistryTest { | ||
|
|
||
| private final PluginRegistry registry = PluginRegistry.getInstance(); | ||
|
|
||
| @Test | ||
| public void discoversBuiltInInputPlugin() { | ||
| Class<?> input = registry.getInputClass("java_stdin"); | ||
| assertNotNull(input); | ||
| assertTrue(Input.class.isAssignableFrom(input)); | ||
| } | ||
|
|
||
| @Test | ||
| public void discoversBuiltInOutputPlugin() { | ||
| Class<?> output = registry.getOutputClass("java_stdout"); | ||
| assertNotNull(output); | ||
| assertTrue(Output.class.isAssignableFrom(output)); | ||
| } | ||
|
|
||
| @Test | ||
| public void discoversBuiltInFilterPlugin() { | ||
| Class<?> filter = registry.getFilterClass("java_uuid"); | ||
| assertNotNull(filter); | ||
| assertTrue(Filter.class.isAssignableFrom(filter)); | ||
| } | ||
|
|
||
| @Test | ||
| public void discoversBuiltInCodecPlugin() { | ||
| Class<?> codec = registry.getCodecClass("java_line"); | ||
| assertNotNull(codec); | ||
| assertTrue(Codec.class.isAssignableFrom(codec)); | ||
| } | ||
| } |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check
resource.toString().contains(\"!\")is fragile and could match false positives. Consider using a more robust approach to detect JAR URLs, such as checking if the URL can be cast to JarURLConnection.