Configuring BRMS 5.0.2 with Oracle 11, JNDI and XA-DS

When deploying BRMS to a production system it is at times important to encrypt the database user’s password in the configuration. By default Jackrabbit does not have means of doing this, however it has the ability to use a data source lookup through JNDI. This gives us an apportunity to use the org.jboss.resource.security.SecureIdentityLoginModule (more on this here http://community.jboss.org/wiki/EncryptingDataSourcePasswords).

Before we start however we have to deal with some technology limitations:

BRMS 5.0.2 ships with Apache Jackrabbit 1.4. When configuring Jackrabbit with Oracle using it’s default org.apache.jackrabbit.core.fs.db.DatabaseFileSystem, you will run into JIRA https://issues.apache.org/jira/browse/JCR-748 (the initialization of the repository will fail due to Oracle converting empty strings to null values). But wait, Jackrabbit provides a specialized implementation for this issue called org.apache.jackrabbit.core.fs.db.OracleFileSystem which is great, however its ability to deal with JNDI is not there in Jackrabbit 1.4 (http://jackrabbit.apache.org/api/1.4/org/apache/jackrabbit/core/fs/db/DbFileSystem.html). This was added in Jackrabbit 1.5 (http://jackrabbit.apache.org/api/1.5/org/apache/jackrabbit/core/fs/db/DbFileSystem.html). OracleFileSystem extends DbFileSystem in case you haven’t noticed 😉

So the first thing we need to do is upgrade BRMS 5.0.2 to use the Jackrabbit 1.5 jars. Thankfully this is just a drop-in jar replacement.

Once you upgrade to Jackrabbit 1.5 and configure your repository.xml to use OracleFileSystem like shown in this small sample:

...
<FileSystem class="org.apache.jackrabbit.core.fs.db.OracleFileSystem">
       <param name="driver" value="javax.naming.InitialContext"/>
       <param name="url" value="jdbc/brms/db"/>
       <param name="schema" value="oracle"/>
       <param name="schemaObjectPrefix" value="FS_"/> 
</FileSystem>
...

And start up your JBoss server instance however another issues comes up described in JIRA https://issues.apache.org/jira/browse/JCR-1907. Basically the AS will wrap the underlying java.sql.Connection to org.jboss.resource.adapter.jdbc.WrappedConnection. The Jackrabbit code however in org.apache.jackrabbit.core.fs.db.OracleFileSystem uses the java.sql.Connection directly which results in the “pretty” ClassCastException shown in the JIRA.

One way to solve this problem is to modify the Jackrabbit source and rebuild the jars, however adding JBoss-specific classes such as org.jboss.resource.adapter.jdbc.WrappedConnection to the Jackrabbit source is not really the best idea, as it makes it JBoss specific. Another way is to actually extend org.apache.jackrabbit.core.fs.db.OracleFileSystem and make a JBoss-specific implementation which can be deployed as an individual jar. For other Appservers then you can create a specialized jar for it and deploy as needed. Even though none are ideal solutions here we show how to do the second option:
We need to modify the OracleFileSystem source ( http://svn.apache.org/viewvc/jackrabbit/tags/1.5.7/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java?view=log ) to fix the ClassCastException.

First we modify the init method:

public class JBossOracleFileSystem extends DbFileSystem { 
...
public void init() throws FileSystemException {
        super.init();

        // initialize oracle.sql.BLOB class & constants

        // use the Connection object for using the exact same
        // class loader that the Oracle driver was loaded with
        try {
        	if (con instanceof WrappedConnection) {
				oracleCon = ((WrappedConnection) con).getUnderlyingConnection();
			}
        	blobClass = oracleCon.getClass().getClassLoader().loadClass("oracle.sql.BLOB");
            durationSessionConstant =
                    new Integer(blobClass.getField("DURATION_SESSION").getInt(null));
            modeReadWriteConstant =
                    new Integer(blobClass.getField("MODE_READWRITE").getInt(null));
        } catch (Exception e) {
            String msg = "failed to load/introspect oracle.sql.BLOB";
            log.error(msg, e);
            throw new FileSystemException(msg, e);
        }
    }
...
}

Then we have to also make sure that “oracleCon” is used when creating the blobs:

 protected Blob createTemporaryBlob(InputStream in) throws Exception {
        ...
        Method createTemporary = blobClass.getMethod("createTemporary",
                new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE});
        Object blob = createTemporary.invoke(null,
                new Object[]{oracleCon, Boolean.FALSE, durationSessionConstant})
       ...

Export this class into a JAR and deploy it in $JBOSS/$CONFIG/deploy/jboss-brms.war/WEB-INF/lib directory. Now in our repository.xml we can use JBossOracleFileSystem, for example:

<FileSystem class="org.jboss.jackrabbit.core.fs.db.JBossOracleFileSystem">
       <param name="driver" value="javax.naming.InitialContext"/>
       <param name="url" value="jdbc/brms/db"/>
       <param name="schema" value="oracle"/>
       <param name="schemaObjectPrefix" value="FSS_"/>
</FileSystem>

Next steps are to define our xa data source in $JBOSS/$CONFIG/deploy/jboss-brms-ds.xml:

<datasources>
    <xa-datasource>
        <jndi-name>jdbc/brms/db</jndi-name>
        <use-java-context>false</use-java-context>
        <track-connection-by-tx>true</track-connection-by-tx>
        <isSameRM-override-value>false</isSameRM-override-value>
        <xa-datasource-class>oracle.jdbc.xa.client.OracleXADataSource</xa-datasource-class>
        <xa-datasource-property name="URL">
            jdbc:oracle:thin:@YOUR_URL_HERE
        </xa-datasource-property>
        <exception-sorter-class-name>
            org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
        </exception-sorter-class-name>
        <no-tx-separate-pools/>
        <security-domain>YOUR_SECURITY_DOMAN</security-domain>
    </xa-datasource>

    <mbean code="org.jboss.resource.adapter.jdbc.vendor.OracleXAExceptionFormatter"
name="jboss.jca:service=OracleXAExceptionFormatter">
        <depends optional-attribute-name="TransactionManagerService">
            jboss:service=TransactionManager
        </depends>
    </mbean>
</datasources>

and define your security domain in $JBOSS/$CONFIG/conf/login-config.xml:

<application-policy name="YOUR_SECURITY_DOMAN">
        <authentication>
            <login-module code="org.jboss.resource.security.SecureIdentityLoginModule" flag="required">
                <module-option name="username">YOUR_DB_USER</module-option>
                <module-option name="password">YOUR_ENCRYPTED_DB_USER_PASSWORD</module-option>
                <module-option name="managedConnectionFactoryName">jboss.jca:name=jdbc/brms/db,service=XATxCM</module-option>
            </login-module>
        </authentication>
    </application-policy>

You can find more information on how to encrypt your password at http://community.jboss.org/wiki/EncryptingDataSourcePasswords

Make sure you have the oracle driver jar in $JBOSS/$CONFIG/lib and now you are using BRMS 5.0.2 with JNDI and XA DS..congrats 😉
Note that this is a non-issue with the upcoming BRMS 5.1 release as well as community Guvnor as they both use Jackrabbit 2.1 where this issue has been fixed as now the code uses ConnectionFactory.unwrap() and does not directly pass the java.sql.Connection when creating blobs:
http://svn.apache.org/viewvc/jackrabbit/tags/2.1.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/Oracle10R1ConnectionHelper.java?revision=934953&view=co

 private Blob createTemporaryBlob(Connection con, InputStream in) throws Exception {
        /*
         * BLOB blob = BLOB.createTemporary(con, false, BLOB.DURATION_SESSION);
         * blob.open(BLOB.MODE_READWRITE); OutputStream out = blob.getBinaryOutputStream(); ... out.flush();
         * out.close(); blob.close(); return blob;
         */
        Method createTemporary =
            blobClass.getMethod("createTemporary", new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE});
        Object blob =
            createTemporary.invoke(null, new Object[]{ConnectionFactory.unwrap(con), Boolean.FALSE,
                    durationSessionConstant});
        Method open = blobClass.getMethod("open", new Class[]{Integer.TYPE}); 
       ...
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: