blob: e29b4967bb3f5d2d42ae4e29f5de38062595ff81 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.drill.exec.store;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.drill.common.config.DrillConfig;
import org.apache.drill.common.exceptions.ExecutionSetupException;
import org.apache.drill.common.logical.StoragePluginConfig;
import org.apache.drill.exec.ExecConstants;
import org.apache.drill.exec.planner.logical.StoragePlugins;
import org.apache.drill.exec.server.DrillbitContext;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Plugin locator for the "classic" class-path method of locating connectors.
* Connectors appear somewhere on the class path, and are subclasses of
* {@link StoragePlugin}. The connector and its configuration class must
* reside in Drill's default class loader.
* <p>
* Handles "classic" storage plugin classes which ship with Drill, or are
* added to Drill's class path.
* <p>
* The plugin registry supports access from multiple threads concurrently.
* This locator is therefore immutable after the {@code init()} call.
* The set of plugin instances and configurations (managed by the registry)
* changes during a run, but the set of known plugin classes is fixed.
* <p>
* We sometimes need to add special plugins for testing. Since we cannot
* add them on the fly, we must add them at (test) startup time via
* config properties.
*
* <h4>Plugin Implementations</h4>
*
* This class manages plugin implementation classes (AKA "connectors")
* which must derive from {@code StoragePlugin}. Each must be configured
* via a class derived from {@code StoragePluginConfig}. No two connectors
* can share a configuration class. Each connector must have exactly one
* associated configuration. (Actually, a single connector might handle
* multiple configurations, but that seems an obscure use case.)
* <p>
* The constructor of the connector associates the two classes, and must
* be of the form:<pre><code>
* public SomePlugin(SomePluginConfig config,
* DrillbitContext context,
* String pluginName) {</code></pre>
* <p>
* Classes must be on the class path. Drill often caches the class path:
* creating it at build time, then storing it in a file, to be read at
* run time. If you are developing a plugin in an IDE, and this class
* refuses to find the plugin, you can temporarly force a runtime
* class path scan via setting the
* {@code ClassPathScanner.IMPLEMENTATIONS_SCAN_CACHE} config property
* to {@code false}. The various test "fixtures" provide an easy way
* to set config properties per-test.
* <p>
* This locator ignores four categories of {@code StoragePluginConfig}
* classes:
* <ul>
* <li>Interfaces or abstract classes</li>
* <li>Those that do not have the constructor described above.</li>
* <li>System plugins with the {@code SystemPlugin} annotation.</li>
* <li>Private test plugins with the {@code PrivatePlugin}
* annotation.</li>
* </ul>
*
* <h4>Config Properties</h4>
*
* <dl>
* <dt>{@code ExecConstants.PRIVATE_CONNECTORS}
* ({@code drill.exec.storage.private_connectors})</dt>
* <dd>An optional list of private plugin class names. Private plugins
* are valid instances of {@code StoragePlugin} which have the
* {@code PrivatePlugin} annotation and so are not automatically
* loaded.</dd>
* </dl>
*/
public class ClassicConnectorLocator implements ConnectorLocator {
private static final Logger logger = LoggerFactory.getLogger(ClassicConnectorLocator.class);
private final PluginRegistryContext context;
// Here "plugin" means storage plugin class
private final Map<Class<? extends StoragePluginConfig>,
Constructor<? extends StoragePlugin>> availablePlugins = new IdentityHashMap<>();
private PluginBootstrapLoader bootstrapLoader;
public ClassicConnectorLocator(PluginRegistryContext context) {
this.context = checkNotNull(context);
this.bootstrapLoader = new PluginBootstrapLoaderImpl(context);
}
// TODO: Provide a list of plugin classes to avoid doing
// the scan twice.
@Override
public void init() {
// Build the list of all available storage plugin class constructors.
final Collection<Class<? extends StoragePlugin>> pluginClasses =
context.classpathScan().getImplementations(StoragePlugin.class);
final String lineBrokenList =
pluginClasses.size() == 0
? "" : "\n\t- " + Joiner.on("\n\t- ").join(pluginClasses);
logger.debug("Found {} storage plugin configuration classes: {}.",
pluginClasses.size(), lineBrokenList);
for (Class<? extends StoragePlugin> plugin : pluginClasses) {
// Skip system and private plugins
if (!plugin.isAnnotationPresent(SystemPlugin.class) &&
!plugin.isAnnotationPresent(PrivatePlugin.class)) {
registerPlugin(plugin);
}
}
// Any private connectors to load?
// Expected to be in the same class loader as this class.
DrillConfig config = context.config();
if (config.hasPath(ExecConstants.PRIVATE_CONNECTORS)) {
List<String> privateConfigs = config.getStringList(ExecConstants.PRIVATE_CONNECTORS);
for (String privateName : privateConfigs) {
registerPrivatePlugin(privateName);
}
}
}
@SuppressWarnings("unchecked")
private void registerPrivatePlugin(String pluginName) {
Class<?> pluginClass;
try {
ClassLoader cl = getClass().getClassLoader();
pluginClass = cl.loadClass(pluginName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Private plugin class not found: " + pluginName, e);
}
if (!StoragePlugin.class.isAssignableFrom(pluginClass)) {
throw new IllegalArgumentException("Private plugin class does not extend StoragePlugin: " + pluginName);
}
if (!registerPlugin((Class<? extends StoragePlugin>) pluginClass)) {
throw new IllegalArgumentException("Private plugin class not valid, see logs: " + pluginName);
}
}
private boolean registerPlugin(Class<? extends StoragePlugin> plugin) {
Map<Class<? extends StoragePluginConfig>, Constructor<? extends StoragePlugin>> ctors = constuctorsFor(plugin);
if (ctors.isEmpty()) {
logger.debug("Skipping registration of StoragePlugin {} as it doesn't have a constructor with the parameters "
+ "of (StoragePluginConfig, Config)", plugin.getCanonicalName());
return false;
} else {
for (Entry<Class<? extends StoragePluginConfig>, Constructor<? extends StoragePlugin>> ctor : ctors.entrySet()) {
if (availablePlugins.containsKey(ctor.getKey())) {
logger.warn(String.format("Two storage plugins cannot use the same config class. " +
"Found conflict %s and %s both use %s. Only the first added to registry.",
availablePlugins.get(ctor.getKey()).getDeclaringClass().getName(),
ctor.getValue().getDeclaringClass().getName(),
ctor.getKey().getName()));
} else {
availablePlugins.put(ctor.getKey(), ctor.getValue());
}
}
return true;
}
}
@SuppressWarnings("unchecked")
public static Map<Class<? extends StoragePluginConfig>, Constructor<? extends StoragePlugin>>
constuctorsFor(Class<? extends StoragePlugin> plugin) {
Map<Class<? extends StoragePluginConfig>, Constructor<? extends StoragePlugin>> ctors = new IdentityHashMap<>();
for (Constructor<?> c : plugin.getConstructors()) {
Class<?>[] params = c.getParameterTypes();
if (params.length != 3
|| !StoragePluginConfig.class.isAssignableFrom(params[0])
|| params[1] != DrillbitContext.class
|| params[2] != String.class) {
logger.debug("Skipping StoragePlugin constructor {} for plugin class {} since it doesn't implement a "
+ "constructor(StoragePluginConfig, DrillbitContext, String)", c, plugin);
continue;
}
Class<? extends StoragePluginConfig> configClass = (Class<? extends StoragePluginConfig>) params[0];
ctors.put(configClass, (Constructor<? extends StoragePlugin>) c);
}
return ctors;
}
@Override
@VisibleForTesting
public Set<Class<? extends StoragePluginConfig>> configClasses() {
return availablePlugins.keySet();
}
/**
* Classic storage plugins do not provide default configurations.
*/
@Override
public StoragePlugin get(String name) {
return null;
}
@Override
public List<StoragePlugin> intrinsicPlugins() {
return null;
}
/**
* Read bootstrap storage plugins
* {@link ExecConstants#BOOTSTRAP_STORAGE_PLUGINS_FILE} and format plugins
* {@link ExecConstants#BOOTSTRAP_FORMAT_PLUGINS_FILE} files for the first
* fresh install of Drill.
*
* @return bootstrap storage plugins
* @throws IOException if a read error occurs
*/
@Override
public StoragePlugins bootstrapPlugins() throws IOException {
return bootstrapLoader.bootstrapPlugins();
}
@Override
public StoragePlugins updatedPlugins() {
return bootstrapLoader.updatedPlugins();
}
@Override
public void onUpgrade() {
bootstrapLoader.onUpgrade();
bootstrapLoader = null;
}
/**
* Creates plugin instance with the given {@code name} and configuration {@code pluginConfig}.
* The plugin need to be present in a list of available plugins and be enabled in the configuration
*
* @param name name of the plugin
* @param pluginConfig plugin configuration
* @return plugin client or {@code null} if plugin is disabled
*/
@Override
public StoragePlugin create(String name, StoragePluginConfig pluginConfig) throws ExecutionSetupException {
StoragePlugin plugin;
Constructor<? extends StoragePlugin> constructor = availablePlugins.get(pluginConfig.getClass());
if (constructor == null) {
throw new ExecutionSetupException(String.format("Failure finding StoragePlugin constructor for config %s",
pluginConfig.getClass().getName()));
}
try {
plugin = constructor.newInstance(pluginConfig, context.drillbitContext(), name);
return plugin;
} catch (ReflectiveOperationException e) {
Throwable t = e instanceof InvocationTargetException ? ((InvocationTargetException) e).getTargetException() : e;
if (t instanceof ExecutionSetupException) {
throw ((ExecutionSetupException) t);
}
throw new ExecutionSetupException(String.format(
"Failure setting up new storage plugin configuration for config %s",
pluginConfig.getClass().getSimpleName()), t);
}
}
@Override
public boolean storable() {
return true;
}
@Override
public Class<? extends StoragePlugin> connectorClassFor(
Class<? extends StoragePluginConfig> configClass) {
Constructor<? extends StoragePlugin> constructor =
availablePlugins.get(configClass);
return constructor == null ? null : constructor.getDeclaringClass();
}
@Override
public void close() { }
}