/*
* 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.kafka.connect.runtime.isolation;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
public class PluginUtils {
private static final String BLACKLIST = "^(?:"
+ "java"
+ "|javax"
+ "|org\\.omg"
+ "|org\\.w3c\\.dom"
+ "|org\\.apache\\.kafka\\.common"
+ "|org\\.apache\\.kafka\\.connect"
+ "|org\\.apache\\.log4j"
+ ")\\..*$";
private static final String WHITELIST = "^org\\.apache\\.kafka\\.connect\\.(?:"
+ "transforms\\.(?!Transformation$).*"
+ "|json\\..*"
+ "|file\\..*"
+ "|converters\\..*"
+ "|storage\\.StringConverter"
+ ")$";
private static final DirectoryStream.Filter<Path> PLUGIN_PATH_FILTER = new DirectoryStream
.Filter<Path>() {
@Override
public boolean accept(Path path) throws IOException {
return Files.isDirectory(path) || PluginUtils.isJar(path);
}
};
public static boolean shouldLoadInIsolation(String name) {
return !(name.matches(BLACKLIST) && !name.matches(WHITELIST));
}
public static boolean isConcrete(Class<?> klass) {
int mod = klass.getModifiers();
return !Modifier.isAbstract(mod) && !Modifier.isInterface(mod);
}
public static boolean isJar(Path path) {
return path.toString().toLowerCase(Locale.ROOT).endsWith(".jar");
}
public static List<URL> pluginUrls(Path pluginPath) throws IOException {
List<URL> urls = new ArrayList<>();
if (PluginUtils.isJar(pluginPath)) {
urls.add(pluginPath.toUri().toURL());
} else if (Files.isDirectory(pluginPath)) {
try (
DirectoryStream<Path> listing = Files.newDirectoryStream(
pluginPath,
PLUGIN_PATH_FILTER
)
) {
for (Path jar : listing) {
urls.add(jar.toUri().toURL());
}
}
}
return urls;
}
public static List<Path> pluginLocations(Path topPath) throws IOException {
List<Path> locations = new ArrayList<>();
// Non-recursive for now. Plugin directories or jars need to be exactly under the topPath.
try (
DirectoryStream<Path> listing = Files.newDirectoryStream(
topPath,
PLUGIN_PATH_FILTER
)
) {
for (Path dir : listing) {
locations.add(dir);
}
}
return locations;
}
public static String simpleName(PluginDesc<?> plugin) {
return plugin.pluginClass().getSimpleName();
}
public static String prunedName(PluginDesc<?> plugin) {
// It's currently simpler to switch on type than do pattern matching.
switch (plugin.type()) {
case SOURCE:
case SINK:
case CONNECTOR:
return prunePluginName(plugin, "Connector");
default:
return prunePluginName(plugin, plugin.type().simpleName());
}
}
public static <U> boolean isAliasUnique(
PluginDesc<U> alias,
Collection<PluginDesc<U>> plugins
) {
boolean matched = false;
for (PluginDesc<U> plugin : plugins) {
if (simpleName(alias).equals(simpleName(plugin))
|| prunedName(alias).equals(prunedName(plugin))) {
if (matched) {
return false;
}
matched = true;
}
}
return true;
}
private static String prunePluginName(PluginDesc<?> plugin, String suffix) {
String simple = plugin.pluginClass().getSimpleName();
int pos = simple.lastIndexOf(suffix);
if (pos > 0) {
return simple.substring(0, pos);
}
return simple;
}
}