Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.seeq.link.agent.DefaultConnectorLoader Maven / Gradle / Ivy
package com.seeq.link.agent;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.seeq.link.agent.interfaces.ConnectorLoader;
import com.seeq.link.sdk.ConnectorV2Host;
import com.seeq.link.sdk.interfaces.Connector;
import com.seeq.link.sdk.interfaces.ConnectorV2;
import com.seeq.link.sdk.utilities.FileSystemGlob;
import lombok.extern.slf4j.Slf4j;
/**
* Loads any JARs found on its ConnectorSearchPaths properties that have classes that implement the {@link Connector}
* interface.
*/
@Slf4j
public class DefaultConnectorLoader implements ConnectorLoader {
/**
* A list of file paths with a terminating file pattern that dictates where the loader will search for JARs.
*
* For example: ../../../seeq-link-xhq-connector/*-connector.jar
*
* Relative and absolute file paths can be supplied. If relative, the folder that the agent JAR is located in is
* used as the root.
*/
private String[] connectorSearchPaths;
/**
* A shared {@link URLClassLoader} instance to use for non-isolated classloading. This is required in Java 11
* because the system classloader can no longer be cast to URLClassLoader.
*/
private URLClassLoader sharedClassLoader;
public String[] getConnectorSearchPaths() {
return this.connectorSearchPaths;
}
@Override
public void initialize(String[] connectorSearchPaths, URLClassLoader sharedClassLoader) {
this.connectorSearchPaths = connectorSearchPaths;
this.sharedClassLoader = sharedClassLoader;
}
public interface ClassEnumerator {
String nextElement();
void close();
}
public static class DirectoryClassEnumerator implements ClassEnumerator {
private final Deque indexStack = new ArrayDeque<>();
private final Deque filesStack = new ArrayDeque<>();
private Integer currentIndex;
private File[] currentFiles;
public DirectoryClassEnumerator(File directory) {
this.currentIndex = 0;
this.currentFiles = directory.listFiles();
}
@Override
public String nextElement() {
while (true) {
if (this.currentIndex >= this.currentFiles.length) {
if (this.indexStack.peek() == null) {
return null;
}
this.currentIndex = this.indexStack.pop();
this.currentFiles = this.filesStack.pop();
this.currentIndex++;
} else {
File file = this.currentFiles[this.currentIndex];
if (file.isDirectory()) {
this.indexStack.push(this.currentIndex);
this.filesStack.push(this.currentFiles);
this.currentIndex = 0;
this.currentFiles = file.listFiles();
} else if (file.getName().endsWith(".class")) {
String pkg = "";
Iterator itIndexStack = this.indexStack.descendingIterator();
Iterator itFilesStack = this.filesStack.descendingIterator();
while (itIndexStack.hasNext()) {
Integer pkgIndex = itIndexStack.next();
File[] pkgFiles = itFilesStack.next();
pkg += pkgFiles[pkgIndex].getName() + ".";
}
this.currentIndex++;
return pkg + file.getName().replace(".class", "");
} else {
this.currentIndex++;
}
}
}
}
@Override
public void close() {
// Nothing to do
}
}
public static class JarClassEnumerator implements ClassEnumerator {
private JarFile jarFile = null;
private Iterator jarFileStreamIterator = null;
public JarClassEnumerator(File jarFilePath) {
try {
this.jarFile = new JarFile(jarFilePath);
this.jarFileStreamIterator = this.jarFile.stream().iterator();
} catch (IOException e) {
LOG.error("Could not open JAR file \"{}\"", jarFilePath, e);
}
}
@Override
public String nextElement() {
if (this.jarFileStreamIterator == null) {
return null;
}
while (this.jarFileStreamIterator.hasNext()) {
JarEntry jarEntry = this.jarFileStreamIterator.next();
if (jarEntry.getName().endsWith(".class")) {
String name = jarEntry.getName();
String className = name.replaceAll("/", ".").replace(".class", "");
return className;
}
}
return null;
}
@Override
public void close() {
try {
this.jarFile.close();
} catch (IOException e) {
// Nothing we can do
LOG.error("Exception closing log file \"{}\"", this.jarFile.getName(), e);
}
}
}
@VisibleForTesting
List loadConnectorJARs() {
return this.loadConnectorJARs(true);
}
/**
* Loads any JARs found on its ConnectorSearchPaths property that have classes that implement the {@link Connector}
* interface, instantiates those objects and returns a list filled with them.
*
* @param isolateClassLoaders
* true if each connector should be loaded with its own ClassLoader
* @return A list filled with objects that implement {@link Connector}.
*/
@Override
public List loadConnectorJARs(boolean isolateClassLoaders) {
// Grab the full path of the Agent JAR that is currently executing
Path executingAssemblyLocation;
try {
executingAssemblyLocation = Paths.get(DefaultConnectorLoader.class.getProtectionDomain().getCodeSource()
.getLocation().toURI());
} catch (URISyntaxException e) {
throw new IllegalStateException("Could not determine executing assembly location", e);
}
Path searchRootPath = executingAssemblyLocation.getParent();
LOG.info("Search root path: '{}'", searchRootPath);
List connectors = new ArrayList<>();
for (String searchPath : this.connectorSearchPaths) {
LOG.info("Searching for connectors using path: '{}'", searchPath);
List connectorJARs = FileSystemGlob.find(searchPath, searchRootPath);
// SQL 2.0 depends on SQL 1.0. Sort the connectors so "sql" is loaded before "sql2".
// If we don't do this we'll get an error: CRAB-12136.
// Loading JARs in a well-defined order should also make runtime behavior more predictable.
//
// CRAB-13850: Whether an OS uses \ or / in the path will affect the sorting order.
// OS independent java sorting order is: /, numbers, \ which means the paths sort as
// sql/, sql2/, sql2\, sql\
// Therefore, replace \ with / before sorting so the order is always the same.
Comparator pathComparator =
Comparator.comparing(s -> s.toString().toLowerCase().replace("\\", "/"));
connectorJARs = connectorJARs.stream()
.sorted(pathComparator)
.collect(ImmutableList.toImmutableList());
boolean atLeastOneJarFound = false;
for (Path filePath : connectorJARs) {
atLeastOneJarFound = true;
LOG.info("Loading candidate JAR: '{}'", filePath);
List classPathUrls = new ArrayList<>();
try {
classPathUrls.add(filePath.toFile().toURI().toURL());
if (filePath.toFile().isFile()) {
this.addLibFolder("lib", filePath, classPathUrls);
this.addLibFolder("nonDistributedLib", filePath, classPathUrls);
}
} catch (Exception e) {
LOG.error("Could not add jar and dependencies to class path for '{}'", filePath, e);
continue;
}
boolean atLeastOneTypeFound;
try {
URLClassLoader classLoaderForConnectors;
if (isolateClassLoaders) {
// Create an isolated class loader so that each connector can load different versions of
// their dependencies.
classLoaderForConnectors = new URLClassLoader(classPathUrls.toArray(new URL[0]),
this.sharedClassLoader);
} else {
classLoaderForConnectors = this.sharedClassLoader;
for (URL url : classPathUrls) {
ClassPathUtilities.addToClassPath(classLoaderForConnectors, url);
}
}
atLeastOneTypeFound = this.searchForConnectors(filePath, classLoaderForConnectors, connectors);
} catch (Throwable e) {
LOG.error("Error loading connector '{}'", filePath, e);
continue;
}
if (!atLeastOneTypeFound) {
LOG.info("No types found in JAR '{}' that implement Connector interface", filePath);
}
}
if (!atLeastOneJarFound) {
LOG.info("No matching JARs found on search path '{}'", searchPath);
}
}
return connectors;
}
private void addLibFolder(String name, Path filePath, List classPathUrls) throws Exception {
Path libFolder = filePath.getParent().resolve(name);
if (libFolder.toFile().exists()) {
LOG.info("lib folder '{}' found, adding JARs to classpath", libFolder);
classPathUrls.addAll(ClassPathUtilities.getJarsInFolder(libFolder.toFile()));
}
}
private boolean searchForConnectors(Path filePath, ClassLoader classLoaderForConnectors, List connectors)
throws IllegalAccessException, ClassNotFoundException {
boolean atLeastOneTypeFound = false;
for (Connector connector : ServiceLoader.load(Connector.class, classLoaderForConnectors)) {
atLeastOneTypeFound |= this.maybeAddConnector(connector, connectors);
}
for (ConnectorV2 connector : ServiceLoader.load(ConnectorV2.class, classLoaderForConnectors)) {
atLeastOneTypeFound |= this.maybeAddConnector(new ConnectorV2Host(connector), connectors);
}
if (atLeastOneTypeFound) {
return true;
} else {
return this.legacySearch(filePath, classLoaderForConnectors, connectors);
}
}
private boolean legacySearch(Path filePath, ClassLoader classLoaderForConnectors, List connectors)
throws ClassNotFoundException, IllegalAccessException {
boolean atLeastOneTypeFound = false;
ClassEnumerator enumerator;
if (filePath.toFile().isFile()) {
enumerator = new JarClassEnumerator(filePath.toFile());
} else {
enumerator = new DirectoryClassEnumerator(filePath.toFile());
}
while (true) {
String className = enumerator.nextElement();
if (className == null) {
break;
}
Class> type = Class.forName(className, true, classLoaderForConnectors);
if (Modifier.isAbstract(type.getModifiers())) {
continue;
}
Connector connector = null;
List> interfaces = Arrays.asList(type.getInterfaces());
if (interfaces.contains(Connector.class)) {
try {
connector = (Connector) type.newInstance();
} catch (InstantiationException e) {
LOG.error("Could not instantiate class '{}', exception:\n", className, e);
continue;
}
} else if (interfaces.contains(ConnectorV2.class)) {
ConnectorV2 connectorV2;
try {
connectorV2 = (ConnectorV2) type.newInstance();
} catch (InstantiationException e) {
LOG.error("Could not instantiate class '{}', exception:\n", className, e);
continue;
}
connector = new ConnectorV2Host(connectorV2);
}
if (connector != null) {
atLeastOneTypeFound |= this.maybeAddConnector(connector, connectors);
}
}
enumerator.close();
return atLeastOneTypeFound;
}
private boolean maybeAddConnector(Connector connector, List connectors) {
boolean alreadyLoaded = false;
for (Connector existingConnectorType : connectors) {
if (existingConnectorType.getName().equals(connector.getName())) {
LOG.info("Connector '{}' already loaded", connector.getName());
alreadyLoaded = true;
break;
}
}
if (!alreadyLoaded) {
connectors.add(connector);
LOG.info("Successfully loaded connector '{}'", connector.getName());
}
return !alreadyLoaded;
}
}