(1). 概述

前面,对:NodeEnvironment进行了分析,这一节主要分析:PluginsService.

(2). Node

protected Node(
            final Environment environment, 
            Collection<Class<? extends Plugin>> classpathPlugins, 
            boolean forbidPrivateIndexSettings) {
    // tmpSettings = {
    //    "cluster.name" : "elasticsearch",
    //     "http.cors.allow-origin" : "*",
    //     "http.cors.enabled" : "true",
    //     "node.name" : "lixin-macbook.local",
    //     "path.home" : "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0",
    //     "path.logs" : "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/logs",   
    //     "client.type" : "node"
    // }

    // TODO ... ...

    // *********************************************************************************
    // 创建插件服务
    // *********************************************************************************
    this.pluginsService = 
            new PluginsService(
                tmpSettings,
                // 配置文件所在目录 
                // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/config
                environment.configFile(), 

                // 模块所在目录
                // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules
                environment.modulesFile(),

                // 插件所在目录 
                // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/plugins
                environment.pluginsFile(), 

                // 空的集合.
                classpathPlugins);      
    // 读取所有插件的:additionalSettings/getFeature中的配置信息
    // 更新到:       settings
    // settings = {
    //    "client.type" : "node",
    //    "cluster.name" : "elasticsearch",
    //    "http.cors.allow-origin" : true,
    //    "http.cors.enabled" : true,
    //    "node.attr.ml.machine_memory" : "8589934592",
    //    "node.attr.ml.max_open_jobs" : "20",
    //    "node.attr.xpack.installed" : true,
    //    "node.name" : "lixin-macbook.local",
    //    "path.home" : "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0",
    //    "path.logs" : "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/logs",
    //    "transport.features.x-pack" : true,
    //    "http.type" : "security4",
    //    "http.type.default" : "netty4",
    //    "transport.type" : "security4" ,
    //    "transport.type.default" : "netty4"
    // }                          
    this.settings = pluginsService.updatedSettings();

    // TODO ... ...
}

(3). PluginsService

public class PluginsService {

    private final Settings settings;
    private final Path configPath;

    private final List<Tuple<PluginInfo, Plugin>> plugins;
    private final PluginsAndModules info;

    public PluginsService(
        Settings settings,
        // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/config
        Path configPath,
        // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules
        Path modulesDirectory,
        // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/plugins
        Path pluginsDirectory,
        // []空的集合
        Collection<Class<? extends Plugin>> classpathPlugins
    ) {

        this.settings = settings;
        this.configPath = configPath;

        List<Tuple<PluginInfo, Plugin>> pluginsLoaded = new ArrayList<>();
        List<PluginInfo> pluginsList = new ArrayList<>();
        final List<String> pluginsNames = new ArrayList<>();

        // classpathPlugins = []
        for (Class<? extends Plugin> pluginClass : classpathPlugins) { 
            Plugin plugin = loadPlugin(pluginClass, settings, configPath);
            PluginInfo pluginInfo = new PluginInfo(pluginClass.getName(), "classpath plugin", "NA", Version.CURRENT, "1.8",
                                                   pluginClass.getName(), Collections.emptyList(), false);
            if (logger.isTraceEnabled()) {
                logger.trace("plugin loaded from classpath [{}]", pluginInfo);
            }
            pluginsLoaded.add(new Tuple<>(pluginInfo, plugin));
            pluginsList.add(pluginInfo);
            pluginsNames.add(pluginInfo.getName());
        } // end for 


        // ********************************************************
        // 加载:modules目录下所有的:molue
        // 这个时候,实际并没有对插件初始化.
        // ********************************************************
        Set<Bundle> seenBundles = new LinkedHashSet<>();
        List<PluginInfo> modulesList = new ArrayList<>();
        // load modules
        if (modulesDirectory != null) { // false
            try {
                // ********************************************************
                // 4.加载模块(PluginsService.getModuleBundles)
                // ********************************************************
                Set<Bundle> modules = getModuleBundles(modulesDirectory);
                for (Bundle bundle : modules) {
                    // modulesList 存储:pluginInfo对象
                    modulesList.add(bundle.plugin);
                }

                // seenBundles存储:所有的:Bundle
                seenBundles.addAll(modules);
            } catch (IOException ex) {
                throw new IllegalStateException("Unable to initialize modules", ex);
            }
        } // end load modules

        // *******************************************************
        // 加载插件目录下的所有文件转换成:Bundle
        // *******************************************************
        // pluginDirectory = "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/plugins"
        if (pluginsDirectory != null) {
            try {
                // 测试下目录是否可读
                if (isAccessibleDirectory(pluginsDirectory, logger)) {
                    // 检查目录下:.removing-这样的文件
                    checkForFailedPluginRemovals(pluginsDirectory);
                    // 加载插件与Modules加载方式一样,不往下分析,可以断点的一点就是:在这个时候:
                    // 插件(jar)里的对象并没有初始化.
                    Set<Bundle> plugins = getPluginBundles(pluginsDirectory);
                    for (final Bundle bundle : plugins) {
                        // pluginsList 存储: PluginInfo对象
                        pluginsList.add(bundle.plugin);
                        // pluginsNames 存储: 配置文件中指定的名称.
                        pluginsNames.add(bundle.plugin.getName());
                    }

                    // 所有的插件.
                    seenBundles.addAll(plugins);
                }
            } catch (IOException ex) {
                throw new IllegalStateException("Unable to initialize plugins", ex);
            }
        }// end load plugins


        // ***********************************************************************
        //    **************** PluginsService.loadBundle比较重要.***************
        // 1. 通过创建:ClassLoader加载jar
        // 2. 根据配置文件(plugin-descriptor.properties)中的配置
        //     [classname=org.elasticsearch.percolator.PercolatorPlugin]
        // 3. ClassLoader加载这个Class(PercolatorPlugin),PercolatorPlugin必须要是:Plugin的子类.
        //    loader.loadClass(className).asSubclass(Plugin.class);
        // 4. 自定义Plugin的构造器只能有三种情况(path:/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/config目录):
        //    4.1 PercolatorPlugin(Settings,Path)
        //    4.2 PercolatorPlugin(Settings)
        //    4.3 PercolatorPlugin()
        // 5. 自定义Plugin如果不满足以上情况,则会抛出IllegalStateException异常.
        // ***********************************************************************
        List<Tuple<PluginInfo, Plugin>> loaded = loadBundles(seenBundles);
        pluginsLoaded.addAll(loaded);

        // PluginsAndModules执有:module/plugin的所有:PluginInfo信息,估计是为了方便索引
        this.info = new PluginsAndModules(pluginsList, modulesList);

        // 初始化后的所有模块和插件,是一个二无组,转换成不可修改集合二元组.
        // 这是不是意味着:不支持热部署哈.
        this.plugins = Collections.unmodifiableList(pluginsLoaded);

        // Checking expected plugins
        List<String> mandatoryPlugins = MANDATORY_SETTING.get(settings);
        if (mandatoryPlugins.isEmpty() == false) { // false
            Set<String> missingPlugins = new HashSet<>();
            for (String mandatoryPlugin : mandatoryPlugins) {
                if (!pluginsNames.contains(mandatoryPlugin) && !missingPlugins.contains(mandatoryPlugin)) {
                    missingPlugins.add(mandatoryPlugin);
                }
            }
            if (!missingPlugins.isEmpty()) {
                final String message = String.format(
                        Locale.ROOT,
                        "missing mandatory plugins [%s], found plugins [%s]",
                        Strings.collectionToDelimitedString(missingPlugins, ", "),
                        Strings.collectionToDelimitedString(pluginsNames, ", "));
                throw new IllegalStateException(message);
            }
        } // end if

        // 打印日志( loaded module [xxxxx] )
        logPluginInfo(info.getModuleInfos(), "module", logger);
        logPluginInfo(info.getPluginInfos(), "plugin", logger);
    }// end PluginsService 构造器
}// end PluginsService

(4). PluginsService.getModuleBundles

// 4.1. 加载modules
// getModuleBundles
// modulesDirectory = "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules"
static Set<Bundle> getModuleBundles(Path modulesDirectory) throws IOException {
    // ***********************************************************
    // 4.2. 调用:findBundles
    // ***********************************************************
    return findBundles(modulesDirectory, "module");
}// end 

// 4.2. findBundles声明
private static Set<Bundle> findBundles(final Path directory, String type) throws IOException {
    // path = "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules"
    // type = "module"


    final Set<Bundle> bundles = new HashSet<>();

    // ******************************************************
    // 4.3. findPluginDirs 
    // ******************************************************
    for (final Path plugin : findPluginDirs(directory)) {
        // plugin为modules下的目录

        // ****************************************************************
        // 5. PluginsService.readPluginBundle
        // 此处以percolator插件为例:   
        // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator
        // 读取插件所有的目录,并转换成:Bundle.
        // ****************************************************************
        final Bundle bundle = readPluginBundle(bundles, plugin, type);

        bundles.add(bundle);
    }
    return bundles;
}// end findBundles

// 4.3 findPluginDirs声明
// 加载目录:/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules下所有的目录
public static List<Path> findPluginDirs(final Path rootPath) throws IOException {

    // [
    //   "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator" ,
    //   "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/reindex" , 
    //    "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/analysis-common"
    // ]
    final List<Path> plugins = new ArrayList<>();

    // percolator = [ "percolator","reindex","analysis-common" ]
    final Set<String> seen = new HashSet<>();
    if (Files.exists(rootPath)) {
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(rootPath)) {
            for (Path plugin : stream) {
                if (FileSystemUtils.isDesktopServicesStore(plugin) ||
                    plugin.getFileName().toString().startsWith(".removing-")) {
                    continue;
                }
                if (seen.add(plugin.getFileName().toString()) == false) {
                    throw new IllegalStateException("duplicate plugin: " + plugin);
                }
                plugins.add(plugin);
            }
        }
    }
    return plugins;
}// end findPluginDirs

(5). PluginsService.readPluginBundle

读取插件信息

private static Bundle readPluginBundle(
            // []空集合
            final Set<Bundle> bundles, 
            // plugin = "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator"
            final Path plugin, 
            // type = "module"
            String type) throws IOException {
        LogManager.getLogger(PluginsService.class).trace("--- adding [{}] [{}]", type, plugin.toAbsolutePath());
        
        final PluginInfo info;
        try {
            // *******************************************************
            // 6. 读取插件目录下的配置文件: PluginInfo.readFromProperties
            // *******************************************************
            info = PluginInfo.readFromProperties(plugin);
        } catch (final IOException e) {
            throw new IllegalStateException("Could not load plugin descriptor for " + type +
                                            " directory [" + plugin.getFileName() + "]", e);
        }

        // *****************************************************
        // 7. 根据:PluginInfo对象,构建成:PluginsService$Bundle
        // Bundle包含一个PluginInfo和一堆Jar包.
        // *****************************************************
        final Bundle bundle = new Bundle(info, plugin);
        // 添加到临时集合中.
        if (bundles.add(bundle) == false) {
            throw new IllegalStateException("duplicate " + type + ": " + info);
        }
        return bundle;
    }

(6). PluginInfo.readFromProperties

读取插件目录下的配置文件,并转换成:PluginInfo对象.

// 查看插件下的:plugin-descriptor.properties 
lixin-macbook:percolator lixin$ cat /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator/plugin-descriptor.properties 

version=7.1.0
name=percolator
classname=org.elasticsearch.percolator.PercolatorPlugin
java.version=1.8
elasticsearch.version=7.1.0
extended.plugins=
has.native.controller=false

readFromProperties读取插件目录下的配置文件,并转换成:PluginInfo对象.

public static PluginInfo readFromProperties(final Path path) throws IOException {
    // ES_PLUGIN_PROPERTIES = "plugin-descriptor.properties";
    // /Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator/plugin-descriptor.properties
    final Path descriptor = path.resolve(ES_PLUGIN_PROPERTIES);

    final Map<String, String> propsMap;
    {
        final Properties props = new Properties();
        // 读取配置文件
        try (InputStream stream = Files.newInputStream(descriptor)) {
            props.load(stream);
        }

        // 将配置文件内容转换到:propsMap里
        propsMap = props.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), props::getProperty));
    }

    // 从配置文件中移除一项:name
    // name = "percolator"
    final String name = propsMap.remove("name");
    if (name == null || name.isEmpty()) {
        throw new IllegalArgumentException(
                "property [name] is missing in [" + descriptor + "]");
    }

    // description = "Percolator module adds capability to index queries and query these queries by specifying documents"
    final String description = propsMap.remove("description");
    if (description == null) {
        throw new IllegalArgumentException(
                "property [description] is missing for plugin [" + name + "]");
    }

    // version = "7.1.0"
    final String version = propsMap.remove("version");
    if (version == null) {
        throw new IllegalArgumentException(
                "property [version] is missing for plugin [" + name + "]");
    }

    // esVersionString = "7.1.0"
    final String esVersionString = propsMap.remove("elasticsearch.version");
    if (esVersionString == null) {
        throw new IllegalArgumentException(
                "property [elasticsearch.version] is missing for plugin [" + name + "]");
    }

    // 对字符串进行解析成:Vesion
    final Version esVersion = Version.fromString(esVersionString);
    // java.version = "1.8"
    final String javaVersionString = propsMap.remove("java.version");
    if (javaVersionString == null) {
        throw new IllegalArgumentException(
                "property [java.version] is missing for plugin [" + name + "]");
    }

    // 检查jdk版本
    JarHell.checkVersionFormat(javaVersionString);

    // classname = "org.elasticsearch.percolator.PercolatorPlugin"
    final String classname = propsMap.remove("classname");
    if (classname == null) {
        throw new IllegalArgumentException(
                "property [classname] is missing for plugin [" + name + "]");
    }

    // extended.plugins = ""
    // 扩展插件以逗号分隔.
    final String extendedString = propsMap.remove("extended.plugins");
    final List<String> extendedPlugins;
    if (extendedString == null) {
        extendedPlugins = Collections.emptyList();
    } else {
        extendedPlugins = Arrays.asList(Strings.delimitedListToStringArray(extendedString, ","));
    }

    // has.native.controller = fasle
    final String hasNativeControllerValue = propsMap.remove("has.native.controller");
    final boolean hasNativeController;
    if (hasNativeControllerValue == null) {
        hasNativeController = false;
    } else {
        switch (hasNativeControllerValue) {
            case "true":
                hasNativeController = true;
                break;
            case "false": // true
                hasNativeController = false;
                break;
            default:
                final String message = String.format(
                        Locale.ROOT,
                        "property [%s] must be [%s], [%s], or unspecified but was [%s]",
                        "has_native_controller",
                        "true",
                        "false",
                        hasNativeControllerValue);
                throw new IllegalArgumentException(message);
        }
    }

    // 针对6.0.0 ~ 6.3.0  删除:   requires.keystore
    if (esVersion.before(Version.V_6_3_0) && esVersion.onOrAfter(Version.V_6_0_0_beta2)) {
        propsMap.remove("requires.keystore");
    }

    // 如果propsMap还有内容(多的配置项),则抛出异常.
    // 看来配置项只能是这几项,否则,抛出异常.所以
    // 这也是为什么从配置文件变成Map,不是从Map中读取数据,而是一项一项的remove.
    if (propsMap.isEmpty() == false) {
        throw new IllegalArgumentException("Unknown properties in plugin descriptor: " + propsMap.keySet());
    }

    // 构建:PluginInfo
    return new PluginInfo(name, description, version, esVersion, javaVersionString,
                            classname, extendedPlugins, hasNativeController);
}

(7). PluginsService$Bundle

Bundle为一个组件,它包含:PluginInfo和所有的Jar包.

static class Bundle {
    final PluginInfo plugin;
    final Set<URL> urls;

    Bundle(PluginInfo plugin, Path dir) throws IOException {
        // plugin = { "name" : "percolator" , "classname" : "org.elasticsearch.percolator.PercolatorPlugin" }
        // dir = "/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator"

        this.plugin = Objects.requireNonNull(plugin);


        Set<URL> urls = new LinkedHashSet<>();
        // gather urls for jar files
        // 过滤目录下:/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator
        // 所有jar文件
        try (DirectoryStream<Path> jarStream = Files.newDirectoryStream(dir, "*.jar")) {
            for (Path jar : jarStream) {
                // file:/Users/lixin/Developer/elastic-search/elasticsearch-7.1.0/modules/percolator/percolator-client-7.1.0.jar
                URL url = jar.toRealPath().toUri().toURL();
                // 添加到临时集合中
                if (urls.add(url) == false) {
                    throw new IllegalStateException("duplicate codebase: " + url);
                }
            }
        }
        this.urls = Objects.requireNonNull(urls);
    } //end 构造器
}

(8). 总结

PluginsService加载所有的modules和plugins.