Skip to content

Commit

Permalink
Add custom class loader to OCF (odpi#8288)
Browse files Browse the repository at this point in the history
Signed-off-by: Mandy Chessell <mandy.e.chessell@gmail.com>
  • Loading branch information
mandy-chessell committed Jul 18, 2024
1 parent 7abc0ee commit e7bf591
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 8 deletions.
4 changes: 2 additions & 2 deletions bom/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ext {
antlrVersion = '3.5.3'
ST4Version = '4.3.4'
avroVersion = '1.11.3'
xtdbVersion = '1.24.3'
xtdbVersion = '1.24.4'
clojureVersion = '1.11.1'
classgraphVersion = '4.8.172'
classmateVersion = '1.5.1'
Expand Down Expand Up @@ -75,7 +75,7 @@ ext {
lang3Version = '3.14.0'
logbackVersion = '1.5.6'
lettuceVersion = '6.3.2.RELEASE'
// TODO: Version 9 now available
// TODO: Lucene Version 9 now available but changed the naming of Codec files and so does not work with XTDB
luceneVersion = '8.11.3'
openlineageVersion = '1.16.0'
ossVersion = '4.16.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
import org.odpi.openmetadata.frameworks.auditlog.AuditLog;
import org.odpi.openmetadata.frameworks.auditlog.AuditLoggingComponent;
import org.odpi.openmetadata.frameworks.auditlog.ComponentDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.odpi.openmetadata.frameworks.connectors.ffdc.ConnectionCheckedException;
import org.odpi.openmetadata.frameworks.connectors.ffdc.ConnectorCheckedException;
import org.odpi.openmetadata.frameworks.connectors.ffdc.OCFErrorCode;
import org.odpi.openmetadata.frameworks.connectors.properties.ConnectionProperties;
import org.odpi.openmetadata.frameworks.connectors.properties.ConnectorTypeProperties;
import org.odpi.openmetadata.frameworks.connectors.properties.beans.Connection;
import org.odpi.openmetadata.frameworks.connectors.properties.beans.ConnectorType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -22,12 +22,10 @@
/**
* ConnectorProviderBase is a base class for a connector provider. It manages all the class loading
* for subclass implementations of the connector provider along with the generation of new connector guids.
*
* ConnectorProviderBase creates a connector instance with the class name from the private variable called
* connectorClassName. This class name is initialized to null. If the getConnector method is called when
* the connectorClassName is null, it throws ConnectorCheckedException.
* This is its default behaviour.
*
* To use the ConnectorProviderBase, create a new class that extends the ConnectorProviderBase class
* and in the constructor call super.setConnectorClassName("your connector's class name");
*/
Expand Down Expand Up @@ -333,7 +331,7 @@ public Connector getConnector(ConnectionProperties connection) throws Connection
*/
try
{
Class<?> connectorClass = Class.forName(connectorClassName);
Class<?> connectorClass = getClassForConnector();
Object potentialConnector = connectorClass.getDeclaredConstructor().newInstance();

connector = (Connector)potentialConnector;
Expand Down Expand Up @@ -406,6 +404,18 @@ public Connector getConnector(ConnectionProperties connection) throws Connection
}


/**
* Use the standard class loader to retrieve the class for the connector.
*
* @return class
* @throws ClassNotFoundException unable to locate a class by that name on the class path
*/
protected Class<?> getClassForConnector() throws ClassNotFoundException
{
return Class.forName(connectorClassName);
}


/**
* Provide a common implementation of hashCode for all OCF Connector Provider objects. The UUID is unique and
* is randomly assigned and so its hashCode is as good as anything to describe the hash code of the properties
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright Contributors to the ODPi Egeria project. */

package org.odpi.openmetadata.frameworks.connectors;


import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;

/**
* IsolatedConnectorClassLoader is used by a connector provider to create a connector instance that uses class
* implementations from its own JAR file rather than any class implementations that may have already been loaded.
*/
public class IsolatedConnectorClassLoader extends URLClassLoader
{
private final ClassLoader defaultClassLoader;
private final JarFile jarfile;
private final String jarFileName;
private final String jarFileSpec;


public IsolatedConnectorClassLoader(String jarFileName) throws IOException
{
this(jarFileName,
Thread.currentThread().getContextClassLoader().getParent(),
Thread.currentThread().getContextClassLoader());
}


/**
* Creates a new class loader of the specified name and using the
* specified parent class loader for delegation.
*
* @param jarFileName name of the jar file to load from
* @param jdkClassLoader the parent class loader for JRE classes
* @param defaultClassLoader the class loader to use if can not find class in JAR.
*/
protected IsolatedConnectorClassLoader(String jarFileName,
ClassLoader jdkClassLoader,
ClassLoader defaultClassLoader) throws IOException
{
super(new URL[]{}, jdkClassLoader);
this.defaultClassLoader = defaultClassLoader;
this. jarFileName = jarFileName;
this.jarfile = new JarFile(jarFileName);
super.addURL(new File(jarFileName).toURI().toURL());

this.jarFileSpec = "jar:file:" + jarFileName + "!/";
super.addURL(new URL(jarFileSpec));
}


/**
* This loads classes from the JDK, then the JAR file, then the default class loader.
*
* @param name The <a href="#binary-name">binary name</a> of the class
*
* @return loaded class
* @throws ClassNotFoundException not found on the class path
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
try
{
return super.findClass(name);
}
catch (ClassNotFoundException notFound)
{
return defaultClassLoader.loadClass(name);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright Contributors to the ODPi Egeria project. */

package org.odpi.openmetadata.frameworks.connectors;


import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;

/**
* IsolatedConnectorProviderBase provides extensions to ConnectorProviderBase that uses a custom class loader to
* load the connector class in getConnector. This custom class loader give preference to the classes in the
* same JAR file as the connector provider's implementation over other classes on the class path.
* Note:
* this process assumes that the connector class is not already loaded through a reference in the connector providers
* implementation - ie private static final String connectorClassName should be set to a literal string.
*
*/
public class IsolatedConnectorProviderBase extends ConnectorProviderBase
{

/**
* Use a custom class loader to favour classes that are located in the connector implementation's JAR file.
*
* @return class
* @throws ClassNotFoundException unable to locate a class by that name on the class path
*/
protected Class<?> getClassForConnector() throws ClassNotFoundException
{
try
{
IsolatedConnectorClassLoader isolatedConnectorClassLoader = new IsolatedConnectorClassLoader(this.getJARFileURL(this.getClass()));

// return Class.forName(getConnectorClassName(), true, isolatedConnectorClassLoader);

return isolatedConnectorClassLoader.loadClass(getConnectorClassName());
}
catch (IOException error)
{
throw new ClassNotFoundException("Bad provider class name", error);
}
}


/**
* Extract the name of the JAR file from the provider's class.
*
* @param providerClass class for this connector provider
* @return JAR file name in URL form
* @throws MalformedURLException should not happen - this means the class was not loaded from a JAR file.
*/
private String getJARFileURL(Class<?> providerClass) throws MalformedURLException
{
URL qualifiedClassURL = providerClass.getResource(providerClass.getSimpleName() + ".class");

if (qualifiedClassURL != null)
{
String qualifiedClassName = qualifiedClassURL.toString();
String noClassQualifiedName = qualifiedClassName.split("!")[0];
String[] noJarQualifiedNameSplit = noClassQualifiedName.split("jar:file:");

String noJarQualifiedName = noJarQualifiedNameSplit[noJarQualifiedNameSplit.length - 1];

return noJarQualifiedName;
}

return null;
}


/**
* Return the path name of the Connector Provider's JAR file.
*
* @return file name of this connector provider's jar file
*/
private String getMyJARFilePath()
{
try
{
return byGetProtectionDomain(this.getClass());
}
catch (Exception error)
{
// Cannot get jar file path using byGetProtectionDomain because the runtime does not permit it
}

return byGetResource(this.getClass());
}



/**
* This method
* @param providerClass
* @return
* @throws URISyntaxException
*/
String byGetProtectionDomain(Class<?> providerClass) throws URISyntaxException
{
URL url = providerClass.getProtectionDomain().getCodeSource().getLocation();
return Paths.get(url.toURI()).toString();
}

String byGetResource(Class<?> providerClass)
{
final URL classResource = providerClass.getResource(providerClass.getSimpleName() + ".class");
if (classResource == null)
{
throw new RuntimeException("class resource is null");
}

final String url = classResource.toString();
if (url.startsWith("jar:file:"))
{
// extract 'file:......jarName.jar' part from the url string
String path = url.replaceAll("^jar:(file:.*[.]jar)!/.*", "$1");
try
{
return Paths.get(new URI(path)).toString();
}
catch (Exception error)
{
throw new RuntimeException("Invalid Jar File URL String", error);
}
}

throw new RuntimeException("Invalid Jar File URL String");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
* The OMRSRepositoryConnectorProviderBase provides a base class for the connector provider supporting OMRS Connectors.
* It adds no function but provides a placeholder for additional function if needed for the creation of
* any OMRS Repository connectors.
*
* It extends ConnectorProviderBase which does the creation of connector instances. The subclasses of
* OMRSRepositoryConnectorProviderBase must initialize ConnectorProviderBase with the Java class
* name of the OMRS Connector implementation (by calling super.setConnectorClassName(className)).
Expand Down

0 comments on commit e7bf591

Please sign in to comment.