/*
* Licensed 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 com.facebook.presto.server;
import com.facebook.presto.block.BlockEncodingManager;
import com.facebook.presto.connector.ConnectorManager;
import com.facebook.presto.eventlistener.EventListenerManager;
import com.facebook.presto.execution.resourceGroups.ResourceGroupManager;
import com.facebook.presto.metadata.Metadata;
import com.facebook.presto.security.AccessControlManager;
import com.facebook.presto.spi.Plugin;
import com.facebook.presto.spi.block.BlockEncodingFactory;
import com.facebook.presto.spi.classloader.ThreadContextClassLoader;
import com.facebook.presto.spi.connector.ConnectorFactory;
import com.facebook.presto.spi.eventlistener.EventListenerFactory;
import com.facebook.presto.spi.resourceGroups.ResourceGroupConfigurationManagerFactory;
import com.facebook.presto.spi.security.SystemAccessControlFactory;
import com.facebook.presto.spi.type.ParametricType;
import com.facebook.presto.spi.type.Type;
import com.facebook.presto.type.TypeRegistry;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import io.airlift.http.server.HttpServerInfo;
import io.airlift.log.Logger;
import io.airlift.node.NodeInfo;
import io.airlift.resolver.ArtifactResolver;
import io.airlift.resolver.DefaultArtifact;
import org.sonatype.aether.artifact.Artifact;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.facebook.presto.metadata.FunctionExtractor.extractFunctions;
import static com.facebook.presto.server.PluginDiscovery.discoverPlugins;
import static com.facebook.presto.server.PluginDiscovery.writePluginServices;
import static java.util.Objects.requireNonNull;
@ThreadSafe
public class PluginManager
{
private static final ImmutableList<String> SPI_PACKAGES = ImmutableList.<String>builder()
.add("com.facebook.presto.spi.")
.add("com.fasterxml.jackson.annotation.")
.add("io.airlift.slice.")
.add("io.airlift.units.")
.add("org.openjdk.jol.")
.build();
private static final Logger log = Logger.get(PluginManager.class);
private final ConnectorManager connectorManager;
private final Metadata metadata;
private final ResourceGroupManager resourceGroupManager;
private final AccessControlManager accessControlManager;
private final EventListenerManager eventListenerManager;
private final BlockEncodingManager blockEncodingManager;
private final TypeRegistry typeRegistry;
private final ArtifactResolver resolver;
private final File installedPluginsDir;
private final List<String> plugins;
private final AtomicBoolean pluginsLoading = new AtomicBoolean();
private final AtomicBoolean pluginsLoaded = new AtomicBoolean();
@Inject
public PluginManager(
NodeInfo nodeInfo,
HttpServerInfo httpServerInfo,
PluginManagerConfig config,
ConnectorManager connectorManager,
Metadata metadata,
ResourceGroupManager resourceGroupManager,
AccessControlManager accessControlManager,
EventListenerManager eventListenerManager,
BlockEncodingManager blockEncodingManager,
TypeRegistry typeRegistry)
{
requireNonNull(nodeInfo, "nodeInfo is null");
requireNonNull(httpServerInfo, "httpServerInfo is null");
requireNonNull(config, "config is null");
installedPluginsDir = config.getInstalledPluginsDir();
if (config.getPlugins() == null) {
this.plugins = ImmutableList.of();
}
else {
this.plugins = ImmutableList.copyOf(config.getPlugins());
}
this.resolver = new ArtifactResolver(config.getMavenLocalRepository(), config.getMavenRemoteRepository());
this.connectorManager = requireNonNull(connectorManager, "connectorManager is null");
this.metadata = requireNonNull(metadata, "metadata is null");
this.resourceGroupManager = requireNonNull(resourceGroupManager, "resourceGroupManager is null");
this.accessControlManager = requireNonNull(accessControlManager, "accessControlManager is null");
this.eventListenerManager = requireNonNull(eventListenerManager, "eventListenerManager is null");
this.blockEncodingManager = requireNonNull(blockEncodingManager, "blockEncodingManager is null");
this.typeRegistry = requireNonNull(typeRegistry, "typeRegistry is null");
}
public void loadPlugins()
throws Exception
{
if (!pluginsLoading.compareAndSet(false, true)) {
return;
}
for (File file : listFiles(installedPluginsDir)) {
if (file.isDirectory()) {
loadPlugin(file.getAbsolutePath());
}
}
for (String plugin : plugins) {
loadPlugin(plugin);
}
metadata.verifyComparableOrderableContract();
pluginsLoaded.set(true);
}
private void loadPlugin(String plugin)
throws Exception
{
log.info("-- Loading plugin %s --", plugin);
URLClassLoader pluginClassLoader = buildClassLoader(plugin);
try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(pluginClassLoader)) {
loadPlugin(pluginClassLoader);
}
log.info("-- Finished loading plugin %s --", plugin);
}
private void loadPlugin(URLClassLoader pluginClassLoader)
throws Exception
{
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, pluginClassLoader);
List<Plugin> plugins = ImmutableList.copyOf(serviceLoader);
if (plugins.isEmpty()) {
log.warn("No service providers of type %s", Plugin.class.getName());
}
for (Plugin plugin : plugins) {
log.info("Installing %s", plugin.getClass().getName());
installPlugin(plugin);
}
}
public void installPlugin(Plugin plugin)
{
for (BlockEncodingFactory<?> blockEncodingFactory : plugin.getBlockEncodingFactories(blockEncodingManager)) {
log.info("Registering block encoding %s", blockEncodingFactory.getName());
blockEncodingManager.addBlockEncodingFactory(blockEncodingFactory);
}
for (Type type : plugin.getTypes()) {
log.info("Registering type %s", type.getTypeSignature());
typeRegistry.addType(type);
}
for (ParametricType parametricType : plugin.getParametricTypes()) {
log.info("Registering parametric type %s", parametricType.getName());
typeRegistry.addParametricType(parametricType);
}
for (ConnectorFactory connectorFactory : plugin.getConnectorFactories()) {
log.info("Registering connector %s", connectorFactory.getName());
connectorManager.addConnectorFactory(connectorFactory);
}
for (Class<?> functionClass : plugin.getFunctions()) {
log.info("Registering functions from %s", functionClass.getName());
metadata.addFunctions(extractFunctions(functionClass));
}
for (ResourceGroupConfigurationManagerFactory configurationManagerFactory : plugin.getResourceGroupConfigurationManagerFactories()) {
log.info("Registering resource group configuration manager %s", configurationManagerFactory.getName());
resourceGroupManager.addConfigurationManagerFactory(configurationManagerFactory);
}
for (SystemAccessControlFactory accessControlFactory : plugin.getSystemAccessControlFactories()) {
log.info("Registering system access control %s", accessControlFactory.getName());
accessControlManager.addSystemAccessControlFactory(accessControlFactory);
}
for (EventListenerFactory eventListenerFactory : plugin.getEventListenerFactories()) {
log.info("Registering event listener %s", eventListenerFactory.getName());
eventListenerManager.addEventListenerFactory(eventListenerFactory);
}
}
private URLClassLoader buildClassLoader(String plugin)
throws Exception
{
File file = new File(plugin);
if (file.isFile() && (file.getName().equals("pom.xml") || file.getName().endsWith(".pom"))) {
return buildClassLoaderFromPom(file);
}
if (file.isDirectory()) {
return buildClassLoaderFromDirectory(file);
}
return buildClassLoaderFromCoordinates(plugin);
}
private URLClassLoader buildClassLoaderFromPom(File pomFile)
throws Exception
{
List<Artifact> artifacts = resolver.resolvePom(pomFile);
URLClassLoader classLoader = createClassLoader(artifacts, pomFile.getPath());
Artifact artifact = artifacts.get(0);
Set<String> plugins = discoverPlugins(artifact, classLoader);
if (!plugins.isEmpty()) {
writePluginServices(plugins, artifact.getFile());
}
return classLoader;
}
private URLClassLoader buildClassLoaderFromDirectory(File dir)
throws Exception
{
log.debug("Classpath for %s:", dir.getName());
List<URL> urls = new ArrayList<>();
for (File file : listFiles(dir)) {
log.debug(" %s", file);
urls.add(file.toURI().toURL());
}
return createClassLoader(urls);
}
private URLClassLoader buildClassLoaderFromCoordinates(String coordinates)
throws Exception
{
Artifact rootArtifact = new DefaultArtifact(coordinates);
List<Artifact> artifacts = resolver.resolveArtifacts(rootArtifact);
return createClassLoader(artifacts, rootArtifact.toString());
}
private URLClassLoader createClassLoader(List<Artifact> artifacts, String name)
throws IOException
{
log.debug("Classpath for %s:", name);
List<URL> urls = new ArrayList<>();
for (Artifact artifact : sortedArtifacts(artifacts)) {
if (artifact.getFile() == null) {
throw new RuntimeException("Could not resolve artifact: " + artifact);
}
File file = artifact.getFile().getCanonicalFile();
log.debug(" %s", file);
urls.add(file.toURI().toURL());
}
return createClassLoader(urls);
}
private URLClassLoader createClassLoader(List<URL> urls)
{
ClassLoader parent = getClass().getClassLoader();
return new PluginClassLoader(urls, parent, SPI_PACKAGES);
}
private static List<File> listFiles(File installedPluginsDir)
{
if (installedPluginsDir != null && installedPluginsDir.isDirectory()) {
File[] files = installedPluginsDir.listFiles();
if (files != null) {
Arrays.sort(files);
return ImmutableList.copyOf(files);
}
}
return ImmutableList.of();
}
private static List<Artifact> sortedArtifacts(List<Artifact> artifacts)
{
List<Artifact> list = new ArrayList<>(artifacts);
Collections.sort(list, Ordering.natural().nullsLast().onResultOf(Artifact::getFile));
return list;
}
}