This post falls in the category of something that took me several hours to figure out and other people are not likely to figure it out on their own and I'm likely to forget it if I don't write it down! The problem is if you have a jar file with some 3rd party library that you want to use in Lucee. Often times this works great, but other times the dependencies of that library may conflict with other jars already loaded into the classpath for Lucee, its bundled libs, or the servlet container.
Jar Dependency Hell
There are several layers of class loaders at play and sometimes you get lucky, but there's been two separate Java libs I've seem people have issues with in CommandBox this last month. One was CFLint and the other was CFParser. Both bundled a very old version of Log4J which is not compatible with the newer Log4J version we bundle in CommandBox/Runwar. While there's a few ways to work around this such as getting the library to update their dependencies, the only surefire way to be able to load a library in such a way that it won't conflicts with the jars elsewhere is to use OSGI. This trick will only work in Lucee as Adobe ColdFusion doesn't employ the power of OSGI. Also, You'll need to be on Lucee 5+.
OSGI is a spec for Java that frameworks like Apache Felix implement whereby each library gets its own custom class loader. Class loaders in Java have a parent/child relationship so when you ask them for a class, if they can't locate it, they defer to their parent. This allows a child class loader to override resolution of classes in a parent loader. Instead of having jars, we call them bundles and each bundle has its own loader so they can exist on their own little island. When you want an instance of a class, you use the OSGI framework for a bundle name and version (yes you can have more than one of the same lib loaded at the same time!) and OSGI will find the correct class loader to load up the exact class you need.
Convert Your Jar
So, the good news is, you don't really need to know or care too much about OSGI. The first step is to ensure the jar file is an OSGI bundle. The easiest way to tell is to crack open the jar (they're just a zip archive with a different extension, 7Zip works great). Look in the /META-INF/MANIFEST.MF file inside the jar and see if there's a line in there that starts with Bundle-SymbolicName. If not, we can turn this jar into an OSGI bundle by simply adding some extra metadata into the manifest file. Here is what I added to the CFLint jar's manifest to make it a proper OSGI bundle.
Bundle-Name: CFLint Bundle-SymbolicName: com.cflint.CFLint Bundle-Description: CFLint Bundle-ManifestVersion: 2 Bundle-Version: 1.4.0
The symbolic name I just made up, but I based it on reverse domain notification so it wouldn't conflict with anything. That's our unique name for this bundle. Since I used 7Zip to edit the file I was able to directly save the MANIFEST.MF file into the archive without ever unzipping everything.
Loading Bundles in Lucee
Now, instead of placing the jar in a lib folder or classloading it through a normal method like javaSettings, we need to have our OSGI framework load up the bundle so it's available to ask for. Sadly Lucee doesn't have any inbuilt way to load a bundle on the fly outside of dropping it into the bundles folder of your server install or packaging it with an extension. Here is the CFML code I came up with that will load an arbitrary OSGI bundle for immediate use at runtime:
CFMLEngine = createObject( "java", "lucee.loader.engine.CFMLEngineFactory" ).getInstance(); OSGiUtil = createObject( "java", "lucee.runtime.osgi.OSGiUtil" ); resource = CFMLEngine.getResourceUtil().toResourceExisting( getPageContext(), expandpath( 'CFLint.jar' ) ); bundle = OSGiUtil.installBundle( CFMLEngine.getBundleContext(), resource, true);
Modify the expandPath( 'CFLint.jar' ) bit to point to the full path of your jar/bundle.
Now that we've done this, there's a couple ways to verify it's been loaded. The first is to log into the server admin and click on the Bundles page and see if it shows in the list of loaded OSGI bundles. The second way to confirm it is with this code:
admin type="server" password="yourServerContextPassword" action="getBundles" returnvariable="bundles"; dump( bundles )
Create Your Classes
Ok, now that the jar is loaded up as an OSGI bundle, we can reference it by name with a good old createObject() call. Note you can add a version number as a 4th parameter to dial in the exact version of the bundle you want.
api = createObject( "java", // The name of the actual Java class we want to create "com.cflint.api.CFLintAPI", // The arbitrary symbolic name of the OSGI bundle we put in the manifest 'com.cflint.CFLint' ).init();
So there you go. OSGI is sort of a scary beast to a lot of people and Lucee's implementation of it is... interesting at times. It will do all sorts of things to try and resolve a bundle including downloading it from the Lucee website if it can. But the benefits of being able to load pretty much any library ever and have zero conflicts with any other core Java lib is massive. Almost all of the core jars in Lucee are wrapped up in OSGI bundles (and extensions) which provides a lot of nice loose coupling. Hopefully this is helpful to anyone else in the same boat.