Chrysalis Resource ManagementChrysalis includes a light-weight resource management API. The key components in Chrysalis resource management are:
For example, a developer could define a
Connection con = (Connection) ResourceLocator.getResource(Connection.class); The Chrysalis resource management framework is very flexible; developers can create factories for any kind of resource needed by their application. Resource FactoriesTo define a new resource factory, the developer must define a
new class that implements the
public class ConnectionFactory implements ResourceFactory { public Class getType() ... public void init(ResourceFactoryConfig config) throws ResourceException ... public Object getResource() throws ResourceException ... } The role of each method is as follows.
Resource Factory TypeThe public Class getType() { return Connection.class; } The Resource Factory InitializationThe public void init(ResourceFactoryConfig config) throws ResourceException { try { this.url = config.getProperty("jdbc.url"); this.userid = config.getProperty("jdbc.userid"); this.password = config.getProperty("jdbc.password"); String driver = config.getProperty("jdbc.driver"); Class.forName(driver).newInstance(); // Load driver } catch (Exception ex) { throw new ResourceException(ex); } } The <factory-for name="java.sql.Connection" default-factory="com.example.ConnectionFactory"> <properties> <property name="jdbc.driver">COM.cloudscape.core.RmiJdbcDriver</property> <property name="jdbc.url">jdbc:rmi://localhost:1099/jdbc:cloudscape:CloudscapeDB</property> <property name="jdbc.userid">Admin</property> <property name="jdbc.password">Secret</property> </properties> </factory-for> Alternately, the factory can retrieve its own configuration directly, from a property or XML file. public void init(ResourceFactoryConfig config) throws ResourceException { try { Properties props = loadProperties(); // Load custom config data this.url = props.getProperty("jdbc.url"); this.userid = props.getProperty("jdbc.userid"); this.password = props.getProperty("jdbc.password"); String driver = props.getProperty("jdbc.driver"); Class.forName(driver).newInstance(); // Load driver } catch (Exception ex) { throw new ResourceException(ex); } } Instance variables can be used to store initialization data
needed later in the Resource RetrievalThe public Object getResource() throws ResourceException { try { Connection con = DriverManager.getConnection(this.url, this.userid, this.password); return con; } catch (SQLException ex) { throw new ResourceException(ex); } } For simple resource factories, the Using FactoriesOnce a resource factory has been defined, you must configure the
resource factory in the ChrysalisConfig.xml file. Once the
factory is properly configured, resources can be retrieved for that
factory using the Factory ConfigurationResource factories must be configured in the ChrysalisConfig.xml file: <config> <!-- Other configuration ... --> <resources> <factories> <factory-for name="java.sql.Connection" default-factory="com.example.ConnectionFactory" /> </factories> </resources> </config> You may need to define more than one factory for a type class of resource, such as separate connection factories for different databases. In this case, you can define additional named resource factories: <config> <!-- Other configuration ... --> <resources> <factories> <factory-for name="java.sql.Connection" default-factory="com.example.ConnectionFactory"> <named-factory factory-name="Database1" class="com.example.db1.ConnectionFactory" /> <named-factory factory-name="Database2" class="com.example.db2.ConnectionFactory" /> </factory-for> </factories> </resources> </config> The Configuration PropertiesThe configuration for a factory can defined using that factory's
<config> <!-- Other configuration ... --> <resources> <factories> <factory-for name="java.sql.Connection" default-factory="com.example.ConnectionFactory"> <properties> <!-- The default factory uses the Cloudscape database --> <property name="jdbc.driver">COM.cloudscape.core.RmiJdbcDriver</property> <property name="jdbc.url">jdbc:rmi://localhost:1099/jdbc:cloudscape:CloudscapeDB</property> <property name="jdbc.userid">Admin</property> <property name="jdbc.password">Secret</property> </properties> <named-factory factory-name="OracleDB" class="com.example.ConnectionFactory"> <properties> <property name="jdbc.driver">oracle.jdbc.driver.OracleDriver</property> <!-- Configuration for Oracle database ... --> </properties> </named-factory> </factory-for> </factories> </resources> </config> There are no pre-defined configuration value. Each resource
factory retrieves its properties by name, using the
Alternately, the full set of configuration properties can be
retrieved as a public class JNDIFactory implements ResourceFactory { private Properties props; public Class getType() { return InitialContext.class; } public void init(ResourceFactoryConfig config) throws ResourceException { this.props = config.getProperties(); } public Object getResource() throws ResourceException { try { return new InitialContext(this.props); } catch(Exception ex) { throw new ResourceException(ex); } } } Locating ResourcesConfigured resource factories can be used via the
Connection con = (Connection) ResourceLocator.getResource(Connection.class); The Connection con = (Connection) ResourceLocator.getResource(Connection.class, "OracleDB"); Managed ResourcesYou can have more sophisticated resource management by using a
managed resource factory. A managed resource factory must
implement the
public class ConnectionFactory implements ManagedResourceFactory { public Class getType() ... public void init(ResourceFactoryConfig config) throws ResourceException ... public Object getResource() throws ResourceException ... public void release(Object resource, Throwable error) throws ResourceException ... } Managed resource factories have the following additional characteristics:
Resource Per Thread of ExecutionOne big advantage of managed resources is that you always get
the same resource within a single thread of execution. For example,
if the Not only does this reduce unnecessary resource creation, it
allows you to perform basic per-thread initialization logic in the
factory's public Object getResource() throws ResourceException { try { Connection con = DriverManager.getConnection(this.url, this.userid, this.password); con.setAutoCommit(false); return con; } catch (SQLException ex) { throw new ResourceException(ex); } } This initialization logic will be performed exactly once per thread of execution, the first time the resource is created for that thread. Resource ReleaseManaged resources are released back to their resource factory
when the thread of execution is complete. The resource is passed to
the factory's public void release(Object resource, Throwable error) throws ResourceException { Connection con = (Connection) resource; try { con.close(); } catch (SQLException ex) { throw new ResourceException(ex); } } The release method takes two parameters, the resource and an
error parameter. If there no errors thrown by the call chain, the
error parameter is public void release(Object resource, Throwable error) throws ResourceException { try { Connection con = (Connection) resource; try { if (error == null) { con.commit(); } else { con.rollback(); } } finally { con.close(); } } catch (SQLException ex) { throw new ResourceException(ex); } } Finally, the release method can be used as a mechanism to support resource pooling. private ConnectionPool pool; // Initialized in init() ... public Object getResource() throws ResourceException { try { return pool.getConnection(); } catch (Exception ex) { throw new ResourceException(ex); } } public void release(Object resource, Throwable error) throws ResourceException { pool.returnToPool((Connection) resource);} The Resource ManagerManaged resources have a lot of advantages, but they also have
one big disadvantage: they can only be used while the
Managed resources can be safely used in:
You can determine whether management is active using the
Connection con = null; if (ResourceLocator.isManagementActive()) { con = (Connection) ResourceLocator.getResource(Connection.class, "Managed"); } else { con = (Connection) ResourceLocator.getResource(Connection.class, "Unmanaged"); } // Perform logic ... if (!ResourceLocator.isManagementActive()) { con.close(); // Manual cleanup for unmanaged resources. } Resource WrappersIf you use managed resources, it is strongly recommended
that the resource be wrapped in an adapter that prevents the
resource from being used incorrectly. For example, you might wrap a
JDBC connection in a custom public class TransactedConnection { final Connection con; // Note the package-level access public TransactedConnection(Connection con) { this.con = con; } public PreparedStatement prepareStatement(String sql) throws SQLException { return con.prepareStatement(sql); } } In the resource factory, the "raw" resource can be wrapped and retrieved in the appropriate places: public class TransactedConnectionFactory implements ManagedResourceFactory { public Class getType() { return TransactedConnection.class; } public void init(ResourceFactoryConfig config) { // Initialization ... } public Object getResource() throws ResourceException { Connection con = // Get connection ... con.setAutoCommit(false); return new TransactedConnection(con); } public void release(Object resource, Throwable error) throws ResourceException { Connection con = ((TransactedConnection)resource).con; // Clean up connection ... } } In client code, the wrapped resource can be used safely without interfering with proper resource management: TransactedConnection con = (TransactedConnection) ResourceLocator.getResource(TransactedConnection.class); PreparedStatement stmt = con.prepareStatement(sql); // Use prepared statement normally ... // Connection cannot be improperly closed ... An even more sophisticated example could have the adapter implement the same interface as the original resource, and intercept client method calls that must be ignored: public class TransactedConnection implements Connection { final Connection con; // Note the package-level access public TransactedConnection(Connection con) { this.con = con; } public PreparedStatement prepareStatement(String sql) throws SQLException { return con.prepareStatement(sql); // Delegate to real connection } public void close() throws SQLException { // Intercept and ignore ... } // Other methods ... } Using the Resource ManagerIf you are using Resources within the Chrysalis web framework, you should not activate the resource manager yourself. The Chrysalis framework will initialize and release the resource manager for you. If you want to use the resource manager independently of the rest of the Chrysalis framework, you are responsible for initializing and releasing the resource manager yourself. ResourceManager.init(); Throwable error = null; try { // Start call chain ... } catch(Throwable e) { error = e; } ResourceManager.release(error); if (error != null) throw error; This works best if all your threads of execution can be invoked through a single front controller. If you are concerned about accidentally initializing the resource manager more than once, using the following code: boolean inactive = ResourceManager.isInitialized(); if (inactive) ResourceManager.init(); Throwable error = null; try { // Start call chain ... } catch(Throwable e) { error = e; } if (inactive) ResourceManager.release(error); if (error != null) throw error; Resource management works by storing data in
Chrysalis resource management will probably also fail within an EJB server environment or other advanced transactional container. These environments already have their own (much more advanced) resource management strategies, which should be used instead. |