Thursday, September 08, 2005

I’ll make the first one a good one!

I said I would write some cool stuff about ColdFusion, right? Well how about I make my first post a good one. I'm currently working with Dave Ross on a really cool project that does some interesting things concerning Mach-ii properties and external configuration files (more on that later). I’m a big mac guy, Dave’s not, so we got into an issue with filepaths, and I suggested maybe loading the config file from the classpath, since the file was really external to the application anyway.

So after I remembered that I was working in coldfusion instead of java, I came up with a cool solution, with ended up using a bit of java. A little backround info is needed to understand how this is done. Whenever you write a cfc or cfm file (or udf), cf compiles the code into a type of java class file, which is basically a normal java object. Since it is a java object you can actually use methods from the base java objects, Object and Class When you run your app, that class is loaded into the jvm that coldfusion is using through a ClassLoader, which you can obtain from the cfc, through base java objects. Before I get too far, here’s a simple example of calling a method from a cfc’s base java objects to show the type of object it is:

<cfcomponent name="test2.cfc">
   <cffunction name="dumpStuff" access="public" returntype="any">
       <cfdump var="#getClass().getName()#" />
   </cffunction>
</cfcomponent>


Kind of cool, because getClass() is a method of java’s Object class and getName() is from the Class (that’s its name) that it returns. So going back loading files, Class has a method getClassLoader() which will return the ClassLoader I mentioned that was used to load that object into the JVM. Why do you want this? Well a ClassLoader can be used to load more that just classes, and the cool thing is it searches through a classpath to find what it’s looking for, which is configured from outside of your application, through CFAdministrator, or Jrun Admin, or through Tomcat, or a .profile file or whatever. And that makes it a very portable way to load things!

Ok, I have a feeling your still wondering why you would want to do this. Remember how I said that Dave was loading things into Mach-ii properties from a config file? Well here’s how and then maybe a little why later. This relies on another Java class, java.util.Properties, that reads a plain text file as key/value pairs and loads them into a similar construct as a cf struct, and even though this sounds like a lot of work, it’s surprisingly easy. We’ll do this in a plugin and have it happen as soon as the app starts up, so first a properties file, I’ll call it ‘myprops.properties’, it looks like this:

datasource=mydsn
mailserver=127.0.0.1
serveradmin=admin@mydomain.com


Now the plugin, with some comments:

<cfcomponent extends="MachII.framework.plugin">

<cffunction name="configure">

   <!--- Java Properties object --->
   <cfset var props = createObject("java","java.util.Properties")/>
   <cfset var propNames = 0/>
   <cfset var prop = 0 />
   <cfset var p = 0 />

   <!--- You would probably want this set elsewhere! --->
   <cfset var propFileName = “myprops.properties “ />
   <cfset var classPathResource = 0 />

   <!--- here we’ll use use a method of ClassLoader to open an InputStream on the property file and tell the Properties object to load it --->

   <cfset classPathResource = getClass().getClassLoader().getResourceAsStream(propFileName)/>

   <cfset props.load(classPathResource)/>

   <!--- now we just use methods from the Properties object to read the values and put them in the Mach-ii property manager using the key names --->

   <cfset propNames = props.keySet().toArray()/>

   <cfloop from="1" to="#arraylen(propNames)#" index="p">
       <cfset prop = propNames[p] />
       <cfset setProperty(prop,props.get(prop))/>
   </cfloop>

</cffunction>

</cfcomponent>


Since this happens as soon as the plugin is loaded, you have those properties available at application startup, and you now have application properties that are configurable from outside of the Mach-ii conf file! Cool, huh? More stuff to come soon…

3 Comments:

Anonymous Anonymous said...

Nice plugin Chris
Unfortunately it doesn't work for me, I always receive the error:
"Variable CLASSPATHRESOURCE is undefined"
>> cfset classPathResource = getClass().getClassLoader().getResourceAsStream(propFileName)

Maybe you have a clue? (I use CFMX 7.0.1)

7:24 AM  
Anonymous Man in a Cube said...

Very creative Chris!

12:07 PM  
Blogger Chris Scott said...

Sorry it took a while to get back about this. GetResourceAsStream(propFileName) returns NULL if the file is not found. Since cf doesn't really do nulls, classPathResource will be undefined if the file is not found in your classpath. Make sure you are putting your properties file somewhere in the classpath that cf knows. You find the 'CF Server Java Class Path' in cfadmin app under settings summary.

5:01 PM  

Post a Comment

<< Home