/*
* 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;
import org.apache.kafka.common.utils.Utils;
import org.apache.kafka.connect.connector.Connector;
import org.apache.kafka.connect.connector.Task;
import org.apache.kafka.connect.errors.ConnectException;
import org.reflections.Reflections;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
public class ConnectorFactory {
public Connector newConnector(String connectorClassOrAlias) {
return instantiate(getConnectorClass(connectorClassOrAlias));
}
public Task newTask(Class<? extends Task> taskClass) {
return instantiate(taskClass);
}
private static <T> T instantiate(Class<? extends T> cls) {
try {
return Utils.newInstance(cls);
} catch (Throwable t) {
throw new ConnectException("Instantiation error", t);
}
}
@SuppressWarnings("unchecked")
private static Class<? extends Connector> getConnectorClass(String connectorClassOrAlias) {
// Avoid the classpath scan if the full class name was provided
try {
Class<?> clazz = Class.forName(connectorClassOrAlias);
if (!Connector.class.isAssignableFrom(clazz))
throw new ConnectException("Class " + connectorClassOrAlias + " does not implement Connector");
return (Class<? extends Connector>) clazz;
} catch (ClassNotFoundException e) {
// Fall through to scan for the alias
}
// Iterate over our entire classpath to find all the connectors and hopefully one of them matches the alias from the connector configration
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forJavaClassPath()));
Set<Class<? extends Connector>> connectors = reflections.getSubTypesOf(Connector.class);
List<Class<? extends Connector>> results = new ArrayList<>();
for (Class<? extends Connector> connector: connectors) {
// Configuration included the class name but not package
if (connector.getSimpleName().equals(connectorClassOrAlias))
results.add(connector);
// Configuration included a short version of the name (i.e. FileStreamSink instead of FileStreamSinkConnector)
if (connector.getSimpleName().equals(connectorClassOrAlias + "Connector"))
results.add(connector);
}
if (results.isEmpty())
throw new ConnectException("Failed to find any class that implements Connector and which name matches " + connectorClassOrAlias +
", available connectors are: " + connectorNames(connectors));
if (results.size() > 1) {
throw new ConnectException("More than one connector matches alias " + connectorClassOrAlias +
". Please use full package and class name instead. Classes found: " + connectorNames(results));
}
// We just validated that we have exactly one result, so this is safe
return results.get(0);
}
private static String connectorNames(Collection<Class<? extends Connector>> connectors) {
StringBuilder names = new StringBuilder();
for (Class<?> c : connectors)
names.append(c.getName()).append(", ");
return names.substring(0, names.toString().length() - 2);
}
}