Tuesday, December 16, 2008

Dynamic config variables in Model-Glue

I've been working on a project at work called Clipboard that tracks RFP text by product and category. I recently updated the application to use Model-Glue 3 and ColdSpring, but was having trouble managing the Model-Glue configuration settings between my development environment and our beta environment. I'd like to deploy the project from one environment to the next without having to change any XML, but didn't quite know how to use dynamic variables inside the Model-Glue XML file. I finally solved the problem using a combination of techniques.

For starters, I'm running ColdFusion 8 and my project setup looks like this:

I’m using the Environment Config project (http://environmentconfig.riaforge.org/) to manage my variables between environments. This XML file returns a struct of variables depending on the environment you’re currently in, which can then be passed into ColdSpring as a set of default properties.

I’m also using Brian Kotek’s DynamicXmlBeanFactory extension for ColdSpring (http://coldspringutils.riaforge.org/) which extends the standard DefaultXMLBeanFactory but allows the ability to replace dynamic properties anywhere in the XML file, as well as in imported XML files.

By combining the Environment Config with the DynamicXmlBeanFactory, I’m able to pass environment-specific variables into ColdSpring and have ColdSpring evaluate them at runtime.

My onApplicationStart() method looks like this:

<cffunction name="onApplicationStart" output="false">

<cfset this.mappings["clipboard"] = expandPath(".") />
<cfset this.mappings["modelglue"] = expandPath("../modelglue/") />
<cfset this.mappings["coldspring"] = expandPath("../coldspring/") />

<cfset application.config = createObject("component","clipboard.config.environment.environment").init("config/environment/environment.xml.cfm").getEnvironmentByUrl(cgi.server_name) />

<cfset application.config.siteroot = getDirectoryFromPath(getMetaData().path) />

<cfset application.beanFactory = createObject("component","coldspring.beanutils.DynamicXmlBeanFactory").init() />
<cfset application.beanFactory.loadBeansFromDynamicXmlFile("config/coldspring/beans.xml",application.config) />

Pretty basic stuff. I'm create mappings for my project, ColdSpring, and Model-Glue, create a struct containing my environment's configuration settings, create a dynamic file path to my project (for uploads), then create my bean factory.

Now on to Model-Glue. Model-Glue uses ColdSpring to manage its internal configuration. With each Model-Glue project, there’s a modelglue.modelGlueConfiguration bean that holds the configuration settings for Model-Glue, such as whether to display debug information, the default event, and paths to your views. It also has a property called “reload” that tells your application whether it should load Model-Glue with each request. This is something you’d want to do in a development environment, but not production as it may take a second to fully load Model-Glue.

Unfortunately, you aren’t able to use dynamic variables inside the Model-Glue XML config file by default, so you’re stuck with having to either manually change the “reload” variable or create an ANT script to modify the XML file with each deployment. Yuck.

Fortunately, there’s a nice little trick I found that will allow you to use dynamic variables. There are a couple lines in the Model-Glue index.cfm file that are commented out by default. The ones we care about are ModelGlue_LOCAL_COLDSPRING_PATH, which specifies where the Model-Glue config file is located, and ModelGlue_PARENT_BEAN_FACTORY, which can be used to specify a parent bean factory to Model-Glue.

Simply put, Model-Glue will look for beans inside its bean factory first (created using ModelGlue_LOCAL_COLDSPRING_PATH), and if it can’t find a bean there, it will check the parent bean factory. Knowing this, we can move the modelglue.modelGlueConfiguration bean out of the Model-Glue bean factory and into the parent bean factory, where we can use the combination of Environment Config and DynamicXmlBeanFactory to use dynamic variables.

Here’s what my index.cfm file looks like:
<cfset ModelGlue_LOCAL_COLDSPRING_PATH = "config\modelglue\empty.xml" />
<cfset ModelGlue_PARENT_BEAN_FACTORY = application.beanFactory />
</cfsilent><cfinclude template="../modelglue/gesture/modelglue.cfm" />
You’ll notice my ModelGlue_LOCAL_COLDSPRING_PATH is set to empty.xml, which contains just <beans></beans> so Model-Glue won’t error out.

Here’s what config/environment/environment.xml.cfm looks like:
<?xml version="1.0" encoding="UTF-8"?>
<property name="development">false</property>
<property name="webpath">/clipboard</property>
<environment id="development">
<property name="datasource">Clipboard_Dev</property>
<property name="development">true</property>
<environment id="beta">
<property name="datasource">Clipboard_Beta</property>
Notice I have a property named “development” that is set to “false” by default, but “true” inside my “development” environment. When I load up my application, I use this XML to create an application.config variable, which is then passed into my dynamic ColdSpring bean factory.

Here’s what my config/coldspring/modelglue.xml file looks like, which now needs to be imported into my beans.xml file as part of Model-Glue's parent bean factory:

<!-- This is your Model-Glue configuration -->
<bean id="modelglue.modelGlueConfiguration" class="ModelGlue.gesture.configuration.ModelGlueConfiguration">

<!-- Be sure to change these to false when you go to production! -->
<property name="reload"><value>${development}</value></property>
<property name="debug"><value>false</value></property>

<!-- Default event-handler -->
<property name="defaultEvent"><value>index</value></property>
<property name="missingEvent"><value>missing</value></property>
<property name="defaultExceptionHandler"><value>error</value></property>

<!-- Controls reloading -->
<property name="reloadPassword"><value>true</value></property>
<property name="reloadKey"><value>init</value></property>

<!-- Where to find necessary files -->
<property name="configurationPath"><value>config/modelglue/modelglue.xml</value></property>
<property name="applicationPath"><value>${webpath}</value></property>
<property name="viewMappings"><value>${webpath}/app/views</value></property>
<property name="helperMappings"><value>${webpath}/app/helpers</value></property>

<!-- Generate unknown events when in development mode? (reload=false) -->
<property name="generationEnabled"><value>false</value></property>

Notice my “reload” property is now set to ${development}, which gets evaluated to true/false when the bean factory is created in my Application.cfc. I’ve also made a couple other variables dynamic using my ${webpath} variable, which could change from one environment to another.

1 comment:

  1. Hi, its been what exactly i m trying to do and its been really helpful, though i m not getting the exact results i m looking for.
    The problem i am facing is... If dump the values, in application.cfc, I get the correct dynamically loaded value for coldspring. but when I try to access these in controllers, i get the strings like ${websiteId} instead of the value defined in environment.xml.cfm file.
    my email id is thesaintbug@gmail.com. Please contact me at this and guide me.