ColdFusion/JRun + Apache Commons Logging
I recently encountered a problem I'd never seen before when taking advantage of powerful Java libraries within CFML components. Since this is really specific to Adobe ColdFusion and Macromedia JRun, it won't be an issue with other configurations. Specifically, here are the details:
- Microsoft Windows “Server” 2003 Standard Edition
- Adobe ColdFusion 8.01 Enterprise as MultiServer
- JavaLoader 1.0 beta
- jXLS 0.9.9-SNAPSHOT
- Apache POI 3.5-FINAL
- Apache Commons BeanUtils 1.8.0
- Apache Commons BeanUtils Collections 1.8.0
- Apache Commons BeanUtils Core 1.8.0
- Apache Commons Collections 3.2.1
- Apache Commons Digester 1.8
- Apache Commons JEXL 1.1
- Apache Commons Logging 1.1.1
The project uses the jXLS XLSTransformer utility class to parse an Excel file and to push information into cells containing syntax like ${bean.prop}. It worked fine on my workstation, but when running on the staging servers, it threw an exception with the following message: User-specified log class 'jrunx.axis.Logging' cannot be found or is not useable.
After many hours of investigation, I tracked the problem down to the so-called discovery process that org.apache.commons.logging.LogFactory uses to provide logger implementations. It was my assumption that when using Mark Mandel's JavaLoader to create instances of classes from the JAR files added to its ClassLoader, they would be isolated from the rest of the JVM. That's not exactly how it works, even if configured not to use ColdFusion's ClassLoader as the parent. To force the LogFactory not to use jrunx.axis.Logging, I tried rebuilding the jXLS library with a commons-logging.properties file to specify which logger implementation to use; I tried adding the properties file to the lib directory. Neither solved the problem.
The solution is pretty simple. After configuring JavaLoader, and before having it instantiate the needed XLSTransformer, just set the desired logger programmatically. Here is the chunk of XML that ColdSpring uses to fill JavaLoader with all the JAR files required.
<bean id="jxlsClassPath" class="model.io.FileEnumerator">
<property name="pathList">
<value>/jxls/lib</value>
</property>
<property name="patternList">
<value>*.jar</value>
</property>
</bean>
<bean id="jxlsFactory" class="jxls.Factory">
<property name="javaloader">
<bean class="javaloader.JavaLoader">
<constructor-arg name="loadPaths">
<bean factory-bean="jxlsClassPath" factory-method="getFileArray"/>
</constructor-arg>
</bean>
</property>
</bean>
The code inside the CFC that does the work of creating the XLSTransformer then explicitly sets the logger:
<cfscript>
var javaLoader = getJavaLoader();
var logFactory = "null";
var transformer = createObject("component", "jxls.Transformer").init();
logFactory = javaLoader.create("org.apache.commons.logging.LogFactory").getFactory();
logFactory.setAttribute("org.apache.commons.logging.LogFactory", "org.apache.commons.logging.impl.LogFactoryImpl");
logFactory.setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
transformer.setXLSTransformer(getJavaLoader().create("net.sf.jxls.transformer.XLSTransformer"));
return transformer;
</cfscript>
There was much celebration when this worked, I assure you.