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

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

There is a newer version: 8.16.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.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toMap;

final class PluginIntrospector {

    private final Set> pluginClasses = Set.of(
        Plugin.class,
        ActionPlugin.class,
        AnalysisPlugin.class,
        CircuitBreakerPlugin.class,
        ClusterPlugin.class,
        DiscoveryPlugin.class,
        EnginePlugin.class,
        ExtensiblePlugin.class,
        HealthPlugin.class,
        IndexStorePlugin.class,
        IngestPlugin.class,
        MapperPlugin.class,
        NetworkPlugin.class,
        PersistentTaskPlugin.class,
        RecoveryPlannerPlugin.class,
        ReloadablePlugin.class,
        RepositoryPlugin.class,
        ScriptPlugin.class,
        SearchPlugin.class,
        ShutdownAwarePlugin.class,
        SystemIndexPlugin.class
    );

    private final Set> deprecatedPluginClasses = pluginClasses.stream()
        .filter(c -> c.isAnnotationPresent(Deprecated.class))
        .collect(Collectors.toUnmodifiableSet());

    private record MethodType(String name, Class[] parameterTypes, boolean isDeprecated) {}

    private final Map, List> pluginMethodsMap;
    private final Map, List> pluginDeprecatedMethodsMap;

    private PluginIntrospector() {
        pluginMethodsMap = pluginClasses.stream().collect(toMap(Function.identity(), PluginIntrospector::findMethods));
        pluginDeprecatedMethodsMap = pluginMethodsMap.entrySet()
            .stream()
            .map(e -> Map.entry(e.getKey(), e.getValue().stream().filter(MethodType::isDeprecated).toList()))
            .filter(e -> e.getValue().isEmpty() == false)
            .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    static PluginIntrospector getInstance() {
        return new PluginIntrospector();
    }

    /**
     * Returns the list of Elasticsearch plugin interfaces implemented by the given plugin
     * implementation class. The list contains the simple names of the interfaces.
     */
    List interfaces(final Class pluginClass) {
        assert Plugin.class.isAssignableFrom(pluginClass);
        return interfaceClasses(pluginClass, pluginClasses::contains).map(Class::getSimpleName).sorted().toList();
    }

    /**
     * Returns the list of methods overridden by the given plugin implementation class. The list
     * contains the simple names of the methods.
     */
    List overriddenMethods(final Class pluginClass) {
        return findOverriddenMethods(pluginClass, pluginMethodsMap).keySet().stream().sorted().toList();
    }

    /**
     * Returns the list of deprecated Elasticsearch plugin interfaces which are implemented by the
     * given plugin implementation class. The list contains the simple names of the interfaces.
     */
    List deprecatedInterfaces(final Class pluginClass) {
        assert Plugin.class.isAssignableFrom(pluginClass);
        return interfaceClasses(pluginClass, deprecatedPluginClasses::contains).map(Class::getSimpleName).sorted().toList();
    }

    /**
     * Returns the deprecated methods from Elasticsearch plugin interfaces which are implemented by
     * the given plugin implementation class. The map is from the simple method name to the simple interface class name.
     *
     * @apiNote The simple names work as a key because they are unique across all plugin interfaces.
     */
    Map deprecatedMethods(final Class pluginClass) {
        return findOverriddenMethods(pluginClass, pluginDeprecatedMethodsMap);
    }

    // finds the subset of given methods that are overridden by the given class
    // returns a map of method name to interface name the method was declared in
    private static Map findOverriddenMethods(final Class pluginClass, Map, List> methodsMap) {
        assert Plugin.class.isAssignableFrom(pluginClass);
        List> clazzes = Stream.concat(Stream.of(Plugin.class), interfaceClasses(pluginClass, methodsMap::containsKey)).toList();
        if (clazzes.isEmpty()) {
            return Map.of();
        }

        Map overriddenMethods = new HashMap<>();
        for (var clazz : clazzes) {
            List methods = methodsMap.get(clazz);
            if (methods == null) {
                continue;
            }
            for (var mt : methods) {
                try {
                    Method m = pluginClass.getMethod(mt.name(), mt.parameterTypes());
                    if (m.getDeclaringClass() == clazz) {
                        // it's not overridden
                    } else {
                        assert clazz.isAssignableFrom(m.getDeclaringClass());
                        var existing = overriddenMethods.put(mt.name(), clazz.getSimpleName());
                        assert existing == null;
                    }
                } catch (NoSuchMethodException unexpected) {
                    throw new AssertionError(unexpected);
                }
            }
        }
        return Map.copyOf(overriddenMethods);
    }

    // Returns the non-static methods declared in the given class.
    @SuppressForbidden(reason = "Need declared methods")
    private static List findMethods(Class cls) {
        assert cls.getName().startsWith("org.elasticsearch.plugins");
        assert cls.isInterface() || cls == Plugin.class : cls;
        return Arrays.stream(cls.getDeclaredMethods())
            .filter(m -> Modifier.isStatic(m.getModifiers()) == false)
            .map(m -> new MethodType(m.getName(), m.getParameterTypes(), m.isAnnotationPresent(Deprecated.class)))
            .toList();
    }

    // Returns a stream of o.e.XXXPlugin interfaces, that the given plugin class implements.
    private static Stream> interfaceClasses(Class pluginClass, Predicate> classPredicate) {
        assert Plugin.class.isAssignableFrom(pluginClass);
        Set> pluginInterfaces = new HashSet<>();
        do {
            Arrays.stream(pluginClass.getInterfaces()).forEach(inf -> superInterfaces(inf, pluginInterfaces, classPredicate));
        } while ((pluginClass = pluginClass.getSuperclass()) != java.lang.Object.class);
        return pluginInterfaces.stream();
    }

    private static void superInterfaces(Class c, Set> interfaces, Predicate> classPredicate) {
        if (classPredicate.test(c)) {
            interfaces.add(c);
        }
        Arrays.stream(c.getInterfaces()).forEach(inf -> superInterfaces(inf, interfaces, classPredicate));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy