Table of Contents | ||||
---|---|---|---|---|
|
A bundle is nothing more than a specialized JAR. There are special attributes within the JAR's manifest that are recognized by the connectors framework. This document describes the plan for connector bundles and classloading strategy.
The goals are as follows:
Eliminate classloading conflicts.
Connectors are the point of integration with third-party systems. This inherently introduces a number of dependencies on third-party jars. As the number of dependencies increase, the chance of a class conflict increases as well.Support different versions of the same connector from a single provisioner.
Often you will have different versions of the same resource that need to be managed from the same provisioner. In these cases, you will typically have multiple versions of the same third-party class and they need to reside in the same JVM.Better patch model for delivering hotfixes.
By their very nature, connectors are one of the most error prone areas of the system, requiring more hotfixes than any other component. It is essential that we provide a way to deliver a patch without impacting other components in the system or other connectors.
For these reasons, each connector will have its own classloader. Bundles are the deployable unit for a connector that contain the connector class and dependencies, thus defining the contents of a connector's classloader.
...
Once built, the bundle becomes a self-contained JAR, with internals as follows:
META-INF/MANIFEST.MF (file)
This is the manifest. Here is a list of the core bundle attributes (used by all bundles, regardless of whether they declare any connectors):ConnectorBundle-FrameworkVersion:
The minimum version of the framework required by the connector. If the version is greater than the version of the deployment, an error will be thrown.ConnectorBundle-!Name:
Qualified name for the connector bundle. Within a given deployment, the pair (ConnectorBundle-Name,ConnectorBundle-Version) must be unique.ConnectorBundle-Version:
The version of the bundle. Within a given deployment, the pair (ConnectorBundle-Name,ConnectorBundle-Version) must be unique.
lib/ (directory)
The libraries (or bundles) distributed with the connector. These are automatically added to the classpath of the bundle at runtime. (This is a non-standard JAR feature, provided by the connectors class loader).
(files)
Source files, classes, and Java resources. (These are standard JAR features). These are intentionally kept as top-level entities to allow you to compile downstream dependencies against the classes within a bundle by simply adding the bundle to your build's classpath. This is to facilitate extending a connector (see "Extending a Connector" below).
Connector Registration
The connector bundle manifest is used merely for the purpose of establishing the classpath and class loader for the bundle. All other meta-data is controlled via Java annotations defined by the Connectors Framework. In order to register a connector, its class must be present in a connector bundle and must have the annotation org.identityconnectors.framework.spi.ConnectorClass. As an implementation note, the Connector Framework scans the top level .class files in a connector bundle, looking for classes that have the @ConnectorClass annotation, thus auto-discovering connectors which are defined in the bundle.
...
Each bundle has its own class loader which resolves classes in the following order:
Classes in the bundle
Classes in JARs in the bundle's lib directory, in alphabetical order.
Note that the JARS in the bundle's lib directory may themselves be bundles which have their own lib directory. These will be recursively processed and added to the classpath in depth-first-search order.Classes in the framework's classloader (and parent class loaders).
As an implementation detail, each bundle will get a BundleClassLoader. A BundleClassLoader is a (subclass of) !URLClassLoader where the URLs are the top-level directory of the bundle, the jars in its lib folder, and the same for each of the embedded bundles. The BundleClassLoader will have, as a parent class loader, the ClassLoader that loaded the connectors framework. The BundleClassLoader will override the appropriate methods such that child class loading happens before parent. In addition, when fetching a class from the parent class loader, it will restrict it such that the class cannot access any internal framework classes.
...
The DatabaseConnector is a great example of this. The common logic would be in a common bundle as follows:
META-INF/MANIFEST.MF
ConnectorBundle-FrameworkVersion: 1.0
ConnectorBundle-Name: org.identityconnectors.database.common
ConnectorBundle-Version: 1.0
org.identityconnectors/database/common/AbstractDatabaseConnector.class
NOTE: this connector would not have a @ConnectorClass annotation.org/identityconnectors/database/common/ (other common source files)
lib/
Then there would be as many database (resource) specific bundles as necessary, for example:
META-INF/MANIFEST.MF
ConnectorBundle-FrameworkVersion: 1.0
ConnectorBundle-Name: org.identityconnectors.database.mysql
ConnectorBundle-Version: 1.0
org/identityconnectors/database/mysql/!MySQLConnector.class (subclass of AbstractDatabaseConnector)
NOTE: this connector would have a @ConnectorClass annotation.org/identityconnectors/database/mysql/ (other !MySQL source files)
lib/org.identityconnectors.database.common-1.0.jar (this is the bundle described above)
lib/ (any specific database drivers and libraries as needed)
Distribution of Connectors
...
A connector should document which versions of the target resource it supports, and these are expected to evolve and mature over time. For the sake of this example, we will use the fictional vendor Humanitrobe. Let's say they have two major product versions to support, Humanitrobe 8.x and their new release, Humanitrobe 9.x. Let's also assume that we do not have the right to distribute the humanitrobe8x.jar or the humanitrobe9x.jar. We already have a HumanitrobeConnector v1.0 in place which supports Humanitrobe 8.x. Regardless of whether their API has changed from release to release, when we have a new version of the same resource, the obvious and intended solution is to produce a new version of the same Connector. In this case we would distribute two separate bundles:
META-INF/MANIFEST.MF
ConnectorBundle-FrameworkVersion: 1.0
ConnectorBundle-Name: org.identityconnectors.humanitrobe
ConnectorBundle-Version: 1.0 #supports 8.x
org/identityconnectors/humanitrobe/HumanitrobeConnector.class (and other sources)
lib/ (directory is empty for distro)
and
META-INF/MANIFEST.MF
ConnectorBundle-FrameworkVersion: 1.0
ConnectorBundle-Name: org.identityconnectors.humanitrobe
ConnectorBundle-Version: 1.1 #supports 9.x
org/identityconnectors/humanitrobe/HumanitrobeConnector.class (and other sources)
lib/ (directory is empty for distro)
Upon deployment these bundles would be re-packaged and the humanitrobe[8x/9x].jar would be added to the lib directories, accordingly.
...