/* * Licensed to ElasticSearch and Shay Banon under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. ElasticSearch licenses this * file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.plugins; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.LifecycleComponent; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.CloseableIndexComponent; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URL; import java.util.*; import static com.google.common.collect.Maps.newHashMap; /** * */ public class PluginsService extends AbstractComponent { private final Environment environment; private final ImmutableMap<String, Plugin> plugins; private final ImmutableMap<Plugin, List<OnModuleReference>> onModuleReferences; static class OnModuleReference { public final Class<? extends Module> moduleClass; public final Method onModuleMethod; OnModuleReference(Class<? extends Module> moduleClass, Method onModuleMethod) { this.moduleClass = moduleClass; this.onModuleMethod = onModuleMethod; } } /** * Constructs a new PluginService * @param settings The settings of the system * @param environment The environment of the system */ public PluginsService(Settings settings, Environment environment) { super(settings); this.environment = environment; Map<String, Plugin> plugins = Maps.newHashMap(); //first we load all the default plugins from the settings String[] defaultPluginsClasses = settings.getAsArray("plugin.types"); for (String pluginClass : defaultPluginsClasses) { Plugin plugin = loadPlugin(pluginClass, settings); plugins.put(plugin.name(), plugin); } // now, find all the ones that are in the classpath loadPluginsIntoClassLoader(); plugins.putAll(loadPluginsFromClasspath(settings)); Set<String> sitePlugins = sitePlugins(); String[] mandatoryPlugins = settings.getAsArray("plugin.mandatory", null); if (mandatoryPlugins != null) { Set<String> missingPlugins = Sets.newHashSet(); for (String mandatoryPlugin : mandatoryPlugins) { if (!plugins.containsKey(mandatoryPlugin) && !sitePlugins.contains(mandatoryPlugin) && !missingPlugins.contains(mandatoryPlugin)) { missingPlugins.add(mandatoryPlugin); } } if (!missingPlugins.isEmpty()) { throw new ElasticSearchException("Missing mandatory plugins [" + Strings.collectionToDelimitedString(missingPlugins, ", ") + "]"); } } logger.info("loaded {}, sites {}", plugins.keySet(), sitePlugins); this.plugins = ImmutableMap.copyOf(plugins); MapBuilder<Plugin, List<OnModuleReference>> onModuleReferences = MapBuilder.newMapBuilder(); for (Plugin plugin : plugins.values()) { List<OnModuleReference> list = Lists.newArrayList(); for (Method method : plugin.getClass().getDeclaredMethods()) { if (!method.getName().equals("onModule")) { continue; } if (method.getParameterTypes().length == 0 || method.getParameterTypes().length > 1) { logger.warn("Plugin: {} implementing onModule with no parameters or more than one parameter", plugin.name()); continue; } Class moduleClass = method.getParameterTypes()[0]; if (!Module.class.isAssignableFrom(moduleClass)) { logger.warn("Plugin: {} implementing onModule by the type is not of Module type {}", plugin.name(), moduleClass); continue; } method.setAccessible(true); list.add(new OnModuleReference(moduleClass, method)); } if (!list.isEmpty()) { onModuleReferences.put(plugin, list); } } this.onModuleReferences = onModuleReferences.immutableMap(); } public ImmutableMap<String, Plugin> plugins() { return plugins; } public void processModules(Iterable<Module> modules) { for (Module module : modules) { processModule(module); } } public void processModule(Module module) { for (Plugin plugin : plugins().values()) { plugin.processModule(module); // see if there are onModule references List<OnModuleReference> references = onModuleReferences.get(plugin); if (references != null) { for (OnModuleReference reference : references) { if (reference.moduleClass.isAssignableFrom(module.getClass())) { try { reference.onModuleMethod.invoke(plugin, module); } catch (Exception e) { logger.warn("plugin {}, failed to invoke custom onModule method", e, plugin.name()); } } } } } } public Settings updatedSettings() { ImmutableSettings.Builder builder = ImmutableSettings.settingsBuilder() .put(this.settings); for (Plugin plugin : plugins.values()) { builder.put(plugin.additionalSettings()); } return builder.build(); } public Collection<Class<? extends Module>> modules() { List<Class<? extends Module>> modules = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { modules.addAll(plugin.modules()); } return modules; } public Collection<Module> modules(Settings settings) { List<Module> modules = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { modules.addAll(plugin.modules(settings)); } return modules; } public Collection<Class<? extends LifecycleComponent>> services() { List<Class<? extends LifecycleComponent>> services = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { services.addAll(plugin.services()); } return services; } public Collection<Class<? extends Module>> indexModules() { List<Class<? extends Module>> modules = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { modules.addAll(plugin.indexModules()); } return modules; } public Collection<Module> indexModules(Settings settings) { List<Module> modules = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { modules.addAll(plugin.indexModules(settings)); } return modules; } public Collection<Class<? extends CloseableIndexComponent>> indexServices() { List<Class<? extends CloseableIndexComponent>> services = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { services.addAll(plugin.indexServices()); } return services; } public Collection<Class<? extends Module>> shardModules() { List<Class<? extends Module>> modules = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { modules.addAll(plugin.shardModules()); } return modules; } public Collection<Module> shardModules(Settings settings) { List<Module> modules = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { modules.addAll(plugin.shardModules(settings)); } return modules; } public Collection<Class<? extends CloseableIndexComponent>> shardServices() { List<Class<? extends CloseableIndexComponent>> services = Lists.newArrayList(); for (Plugin plugin : plugins.values()) { services.addAll(plugin.shardServices()); } return services; } private Set<String> sitePlugins() { File pluginsFile = environment.pluginsFile(); Set<String> sitePlugins = Sets.newHashSet(); if (!pluginsFile.exists()) { return sitePlugins; } if (!pluginsFile.isDirectory()) { return sitePlugins; } File[] pluginsFiles = pluginsFile.listFiles(); for (File pluginFile : pluginsFiles) { if (new File(pluginFile, "_site").exists()) { sitePlugins.add(pluginFile.getName()); } } return sitePlugins; } private void loadPluginsIntoClassLoader() { File pluginsFile = environment.pluginsFile(); if (!pluginsFile.exists()) { return; } if (!pluginsFile.isDirectory()) { return; } ClassLoader classLoader = settings.getClassLoader(); Class classLoaderClass = classLoader.getClass(); Method addURL = null; while (!classLoaderClass.equals(Object.class)) { try { addURL = classLoaderClass.getDeclaredMethod("addURL", URL.class); addURL.setAccessible(true); break; } catch (NoSuchMethodException e) { // no method, try the parent classLoaderClass = classLoaderClass.getSuperclass(); } } if (addURL == null) { logger.debug("failed to find addURL method on classLoader [" + classLoader + "] to add methods"); return; } File[] pluginsFiles = pluginsFile.listFiles(); for (File pluginFile : pluginsFiles) { if (pluginFile.isDirectory()) { logger.trace("--- adding plugin [" + pluginFile.getAbsolutePath() + "]"); try { // add the root addURL.invoke(classLoader, pluginFile.toURI().toURL()); // gather files to add List<File> libFiles = Lists.newArrayList(); if (pluginFile.listFiles() != null) { libFiles.addAll(Arrays.asList(pluginFile.listFiles())); } File libLocation = new File(pluginFile, "lib"); if (libLocation.exists() && libLocation.isDirectory() && libLocation.listFiles() != null) { libFiles.addAll(Arrays.asList(libLocation.listFiles())); } // if there are jars in it, add it as well for (File libFile : libFiles) { if (!(libFile.getName().endsWith(".jar") || libFile.getName().endsWith(".zip"))) { continue; } addURL.invoke(classLoader, libFile.toURI().toURL()); } } catch (Exception e) { logger.warn("failed to add plugin [" + pluginFile + "]", e); } } } } private Map<String, Plugin> loadPluginsFromClasspath(Settings settings) { Map<String, Plugin> plugins = newHashMap(); Enumeration<URL> pluginUrls = null; try { pluginUrls = settings.getClassLoader().getResources("es-plugin.properties"); } catch (IOException e) { logger.warn("failed to find plugins from classpath", e); return ImmutableMap.of(); } while (pluginUrls.hasMoreElements()) { URL pluginUrl = pluginUrls.nextElement(); Properties pluginProps = new Properties(); InputStream is = null; try { is = pluginUrl.openStream(); pluginProps.load(is); String pluginClassName = pluginProps.getProperty("plugin"); Plugin plugin = loadPlugin(pluginClassName, settings); plugins.put(plugin.name(), plugin); } catch (Exception e) { logger.warn("failed to load plugin from [" + pluginUrl + "]", e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { // ignore } } } } return plugins; } private Plugin loadPlugin(String className, Settings settings) { try { Class<? extends Plugin> pluginClass = (Class<? extends Plugin>) settings.getClassLoader().loadClass(className); try { return pluginClass.getConstructor(Settings.class).newInstance(settings); } catch (NoSuchMethodException e) { try { return pluginClass.getConstructor().newInstance(); } catch (NoSuchMethodException e1) { throw new ElasticSearchException("No constructor for [" + pluginClass + "]. A plugin class must " + "have either an empty default constructor or a single argument constructor accepting a " + "Settings instance"); } } } catch (Exception e) { throw new ElasticSearchException("Failed to load plugin class [" + className + "]", e); } } }