Session Context in the pool
To understand the use case you must know the core of the ObjectPool and the PoolableConnector relation.
The PoolableConnector instances are stored in multiple pools and each pool identified by a unique ConnectorPoolKey which is composed by ConnectorKey, ConfigurationProperties and ObjectPoolConfiguration. This works well when each PoolableConnector instances init and dispose a single reusable remote connection (for example a JDBC Connection). Each operation can borrow and existing PoolableConnector instance (Only one thread use this instance at that time, so their thread safety is guarantied!) from the pool or create a new one if the pool is empty and has available capacity. The PoolableConnector instances are disposed after certain idle time. This is the simple use case but most of the time the situation is more complex because Some third-party libraries are involved.
The third-party Pool implementations and different SDKs have more advances requirements and some SDK offers only pooled connection. In such case there is no best practice to implement connector. If a simple Connector implementation creates a new external pool then borrow one connection and finally dispose the entire pool for each operation is not optimal. If a PoolableConnector implementation creates a new external pool and borrow always one connection then this is not optimal either.
The only workaround to share such "Object" (pool) is to create a static Map<"Key", "Object"> variable and get the external "Object" from there.
- First questions, what should be the "Key" value to avoid reusing the other "Object" made by other connector?
- The OpenICF use the ConnectorPoolKey but it's not accessible. :(
If there is a pool already then how easy would be to put the "Object" there so the Framework would guarantee that the PoolableConnector instances from the same pool can access the same "Object".
This is where the ConnectorContext would fit in. This would allow to implement a new type of poolable Connector and this connector can init a third-party pool and share it with the other instance from the same ObjectPool.
The use case I have is the lifecycle of the third-party pool. When the first PoolableConnector instance is created it has to initiate the third-party pool and when the last PoolableConnector instance is disposed it has to shutdown the third-party pool.
- Before the first SharedPoolableConnector instance is used it has to initiates the ConnectorContext variables.
- After the last SharedPoolableConnector instance is disposed it also dispose the ConnectorContext variables.
The proposed new interface would work the following (Mind the consideration at the bottom!):
First thread creates the new ObjectPool with the given ObjectPoolConfiguration (See comments below). The pool creates a new Connector instance and inits it with Configuration. So far no change. Because this instance is the first and implements the new SharedPoolableConnector interface the Pool calls the builderConnectorContext method which must return with a new ConnectorContext. This ConnectorContext will be shared between the all instances in this pool. The Operation implementation passes this ConnectorContext within the OperationOptions.
When the last Connector is disposed from the pool it calls the dispose. The dispose methods can be added to ConnectorContext or the disposeConnectorContext may be added to the SharedPoolableConnector. I don't know pros and cons. Maybe if the ConnectorContext has its own dispose method the Pool can dispose it even if there is no Connector instance available in the pool.
package org.identityconnectors.framework.spi; import org.identityconnectors.framework.common.objects.OperationOptions; /** * An SharedPoolableConnector provides ThreadSafe access to shared resources * between different connector instances within the same * {@link org.identityconnectors.framework.impl.api.local.ObjectPool}. * <p/> * A connector instance can create a custom connection pool and share it with * the other instances withing the same pool. * * @author Laszlo Hordos * @since 1.4 */ public interface SharedPoolableConnector<C extends Configuration> extends Connector { public interface ConnectorContext { /** * Dispose of any resources the {@link Connector} uses. * * Needs to investigate the Prosa and cons with the {SharedPoolableConnector#disposeConnectorContext} *PRO: If the Connector is disposed and new created then avoid to dispose and init in same loop. */ public void dispose(); } public class OperationOptions { public ConnectorContext getContext() { return getOptions().get("__CONTEXT__"); } } /** * Builds a new {@link ConnectorContext} instance. * * This method called only once after the {@code Connector#init} when the * pool creates the first instance. This is a syncronized thread safe * method. * * @ThreadSafe */ public ConnectorContext builderConnectorContext(); /** * Dispose of any resources the {@link ConnectorContext} uses. * * This method is called once on the last instance disposed from the * ObjectPool. * * @ThreadSafe */ public void disposeConnectorContext(ConnectorContext context); /** * Checks if the connector is still alive. * * <p> * A connector can spend a large amount of time in the pool before being * used. This method is intended to check if the connector is alive and * operations can be invoked on it (for instance, an implementation would * check that the connector's physical connection to the resource has not * timed out). * </p> * * <p> * The major difference between this method and * {@link org.identityconnectors.framework.spi.operations.TestOp#test()} is * that this method must do only the minimum that is necessary to check that * the connector is still alive. <code>TestOp.test()</code> does a more * thorough check of the environment specified in the Configuration, and can * therefore be much slower. * </p> * * <p> * This method can be called often. Implementations should do their best to * keep this method fast. * </p> * * @param context * the shared ConnectorContext instance. * @throws RuntimeException * if the connector is no longer alive. */ public void checkAlive(ConnectorContext context); }
Additional notes on the ObjectPoolConfiguration:
Unfortunately this come with some additional complexity because now there are two kinds of pools are created and these has to be configured some way. For example ObjectPool has max 10 and the third-party pool has max 8 means 2 PoolableConnector instances may not able to connect. Another use case when the configuration and the pool configuration should be in sync. The connector must connect to 3270 terminal and only one connection is allowed. This case the maxObjects must be set to 1. This is where I don't want to overcomplicate the connector interface and add an extra methods to synchronise them. This raise a risk of misconfiguring the pools but I think if it's written in the documentation how to keep the connector Configuration and the ConnectorPool configuration in sync then leave the responsibility for the Configurator to do so. These use case are just and addition to ObjectPoolConfiguration pre-syncronization/validation but if you agree then let's drop it from the Framework.
For consideration:
Non Pooled Connector:
The Framework creates a new instance of the Connector class, initialises it with Configuration, calls the operation and disposes it finally.
Pooled Connector:
The Framework creates a pool and borrow a Connector instance. The borrow implementation takes one existing pooled instance from the pool or creates a new instance of the Connector class, initialises it with Configuration and then return with it. The framework calls the operation and puts it back to pool.
Shared Pooled Connector #1:
The Framework creates a pool and borrow a Connector instance. The borrow implementation takes one existing pooled instance and the ConnectorContext from the pool or creates a new instance of the Connector class, initialises it with Configuration and create a new ConnectorContext if it's the first entry in the pool. The framework calls the operation on that connector instance with the ConnectorContext and puts it back to pool.
Shared Pooled Connector #2:
The Framework creates a pool and borrow a Connector instance. The borrow implementation always creates a new instance of the Connector class, initialises it with Configuration and create a new ConnectorContext if it's the first entry in the pool. The framework calls the operation on that connector instance with the ConnectorContext and disposes it finally.
The last would ignore the Framework ObjectPoolConfiguration configuration and rely on the ConnectorContext.