Chrysalis Resource Management

Chrysalis includes a light-weight resource management API. The key components in Chrysalis resource management are:

  • ResourceFactory: An interface for developer-defined factories that create a specific kind of resource.
  • ResourceLocator: A utility class used by Chrysalis controllers and beans to retrieve resources.

For example, a developer could define a ConnectionFactory to create JDBC connections. Other classes could then retrieve connections with the following code:

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 Factories

To define a new resource factory, the developer must define a new class that implements the org.chwf.resources.ResourceFactory interface:

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.

  • getType(): Defines the type of resource returned by this factory.
  • init(): Initializes the factory.
  • getResource(): Creates and returns the resource.

Resource Factory Type

The getType() method indicates the kind of resource generated by this factory. It should always return the same value.

public Class getType() {
  return Connection.class;
}

The getResource() method must return objects of this type, including any subclass of this type.

Resource Factory Initialization

The init() method is invoked exactly once for each ResourceFactory, allowing it to prepare for resource retrieval. The factory configuration can be retrieved from the ResourceFactoryConfig object:

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 ResourceFactoryConfig object gets its configuration information from the ChrysalisConfig.xml file, within that factory's <properties> tag.

<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 getResource() method, as is the case for url, userid and password in the example above. The init() method can also be used to create special classes required for the specific resource, such as the JDBC driver in the example above.

Resource Retrieval

The getResource() method creates and returns the specified resource.

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 getResource() method should create a new resource every time this method is called. If you need a more sophisticated resource management strategy use a managed resource factory (discussed below).

Using Factories

Once 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 ResourceLocator class.

Factory Configuration

Resource 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 <factory-for> tag defines the "default factory" for that type of resource. The <named-factory> tags define specialized resource factories. Each named factory can either be a different factory class, or the same factory class with a different configuration (as discussed below).

Configuration Properties

The configuration for a factory can defined using that factory's <properties> tag. The default factory and each named factory can have different configurations. This means that each named factory can use the same factory class, but have a different configuration.

<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 ResourceFactoryConfig object's getProperty() method.

Alternately, the full set of configuration properties can be retrieved as a java.util.Properties object via the getProperties() method. This is especially useful for resources like the JNDI InitialContext, that are initialized with a Properties object.

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 Resources

Configured resource factories can be used via the ResourceLocator.getResource() method. This method returns a generic Object, so the return value must be cast to the correct type.

Connection con = (Connection) ResourceLocator.getResource(Connection.class);

The ResourceLocator.getResource() method delegates to the getResource() method of the default resource factory. To use a named resource factory, pass the factory name as an additional method parameter, as specified in the factory-name attribute of the <named-factory> configuration tag.

Connection con =
    (Connection) ResourceLocator.getResource(Connection.class, "OracleDB");

Managed Resources

You can have more sophisticated resource management by using a managed resource factory. A managed resource factory must implement the org.chwf.resources.ManagedResourceFactory interface. This is a sub-interface of the ResourceFactory interface, with the addition of a release() method:

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-Execution: The ResourceLocator.getResource() method will always return the same resource within a single thread of execution.
  • Resource Release: Resources are released back to their resource factory when the thread of execution is complete.
  • Requires the Resource Manager: Unlike simple resources, managed resources can only be retrieved when the resource manager is active.

Resource Per Thread of Execution

One big advantage of managed resources is that you always get the same resource within a single thread of execution. For example, if the ConnectionFactory where a managed resource factory, then ResourceLocator.getResource(Connection.class) would return the same connection over and over again within a single call chain.

Not only does this reduce unnecessary resource creation, it allows you to perform basic per-thread initialization logic in the factory's getResource() method. For example, a ConnectionFactory could setAutoCommit(false), in order to make JDBC operations transactional:

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 Release

Managed resources are released back to their resource factory when the thread of execution is complete. The resource is passed to the factory's release() method, which can be used for resource cleanup.

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 null. Otherwise, this parameter is the error thrown during the thread of execution. The error parameter can be used as a signal for simple transaction management:

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 Manager

Managed resources have a lot of advantages, but they also have one big disadvantage: they can only be used while the ResourceManager is active. In the Chrysalis engine, the ResourceManager is active for normal Controller method and page invocations, but not for any initialization logic.

Managed resources can be safely used in:

  • Controller methods.
  • Page views.
  • Other classes using only within Controller methods or page views.

You can determine whether management is active using the ResourceLocator.isManagementActive() method. This allows you to use an alternate (non-managed) resource if you need to, using different named factories for managed and unmanaged resources.

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 Wrappers

If 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 TransactedConnection.

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 Manager

If 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 ThreadLocal variables, and will not propagate resources to new threads. This is a good thing; it ensures that a resource will never be used simultaneously by multiple threads.

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.