More on Extending ColdSpring: A Custom BeanFactory

The more I dig into how ColdSpring works the more impressed I am with it (and I was already hella impressed). I'm realizing that a lot of the utility and helper CFCs I've created and released on RIAForge can actually be implemented directly within ColdSpring by extending it.

This time I want to look at creating a custom BeanFactory. If you use ColdSpring you're probably very familiar with the DefaultXMLBeanFactory. It's the workhorse, the big cheese of the ColdSpring Empire. It's basically your interface to the framework and you ask it to do just about everything.

I recently released an XML utility that will let developers specify dynamic property values anywhere in their ColdSpring XML. This is really cool and I have used the heck out of it to make things much more dynamic and flexible. But I realized that I could do the same thing directly within ColdSpring if I created my own BeanFactory instead of having to use a separate utility.

Well, it works like a charm. So now, I can do this:

<cfset dynamicProperties = StructNew() />
<cfset dynamicProperties.servicePackage = "myapp.components.services" />

<!--- Load the ColdSpring Dynamic XML Bean Factory, which will replace any dynamic values in the XML with matching properties I specified. --->
<cfset application.beanFactory = CreateObject('component', 'myapp.components.factory.DynamicXmlBeanFactory').init() />
<cfset application.beanFactory.loadBeansFromDynamicXmlFile('/myapp/config/coldspring.xml', dynamicProperties) />

		

I did this by extending the DefaultXMLBeanFactory and adding my own custom method, loadBeansFromDynamicXML(), which does the work of reading in the XML files, related import files, and replaces the properties before it hands the XML off to the parent BeanFactory for normal processing. I'll post the code below (even though it could do with some cleaning up), but will try to get this into RIAForge as well:

<cfcomponent name="DynamicXmlBeanFactory" 
			displayname="DynamicXmlBeanFactory" 
			extends="coldspring.beans.DefaultXmlBeanFactory"
			hint="Dynamic XML Bean Factory implimentation" 
			output="false">
	
	<cffunction name="loadBeansFromDynamicXmlFile" returntype="void" access="public" hint="loads bean definitions into the bean factory from an xml file location and replace all dynamic properties">
		<cfargument name="beanDefinitionFile" type="string" required="true" hint="I am the location of the dynamic bean definition xml file"/>
		<cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="Structure containing the dynamic properties to be replaced. The key names must match the dynamic properties in the XML file." />
		<cfset loadDynamicColdSpring(arguments.beanDefinitionFile, arguments.properties) />
	</cffunction>
	
	<cffunction name="loadDynamicColdSpring" access="public" returntype="void" output="false" hint="I replace any dynamic properties in the specified ColdSpring XML file and return the bean factory.">
		<cfargument name="coldSpringXMLPath" type="string" required="true" hint="Path to ColdSpring XML File i.e. '/myapp/config/coldspring.xml'" />
		<cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="Structure containing the dynamic properties to be replaced. The key names must match the dynamic properties in the XML file." />
		<cfset var local = StructNew() />
		<cfset local.replacedColdSpringXML = getReplacedColdSpringXML(arguments.coldSpringXMLPath, arguments.properties) />
		<cfset loadBeansFromXmlRaw(beanDefinitionXml=local.replacedColdSpringXML, constructNonLazyBeans=true) />
	</cffunction>
	
	<cffunction name="getReplacedColdSpringXML" access="public" returntype="string" output="false" hint="I return the ColdSpring XML with all imports processed and dynamic properties replaced.">
		<cfargument name="coldSpringXMLPath" type="string" required="true" hint="Path to ColdSpring XML File i.e. '/myapp/config/coldspring.xml'" />
		<cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="Structure containing the dynamic properties to be replaced. The key names must match the dynamic properties in the XML file." />
		<cfset var local = StructNew() />
		<cfset local.imports = StructNew() />
		<cfset findImports(local.imports, arguments.coldSpringXMLPath) />
		<cfif StructCount(local.imports) eq 1>
			<cfset arguments.coldSpringXMLPath = ExpandPath(arguments.coldSpringXMLPath) />
			<cfset local.replacedColdSpringXML = replaceDynamicValues(arguments.coldSpringXMLPath, arguments.properties) />		
		<cfelseif StructCount(local.imports) gt 1>
			<cfset local.replacedXMLArray = ArrayNew(1) />
			<cfloop collection="#local.imports#" item="local.thisImport">
				<cfset local.tempImportData = StructNew() />
				<cfset local.tempImportData.importFile = local.thisImport />
				<cfset local.tempImportData.replacedXML = replaceDynamicValues(local.thisImport, arguments.properties) />
				<cfset ArrayAppend(local.replacedXMLArray, local.tempImportData) />
			</cfloop>
			<cfset local.replacedColdSpringXML = '<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">#Chr(13)##Chr(10)#<beans>#Chr(13)##Chr(10)##Chr(13)##Chr(10)#' />
			<cfloop from="1" to="#ArrayLen(local.replacedXMLArray)#" index="local.thisXML">
				<cfset local.replacedColdSpringXML = local.replacedColdSpringXML & '#Chr(13)##Chr(10)##Chr(9)#<!-- @import processed from #local.replacedXMLArray[local.thisXML].importFile# -->#Chr(13)##Chr(10)#' & ReReplaceNoCase(local.replacedXMLArray[local.thisXML].replacedXML, '.*<beans>|<import[^>]*>|</beans>', '', 'All') />	
			</cfloop>			
			<cfset local.replacedColdSpringXML = local.replacedColdSpringXML & '</beans>' />
		</cfif>
		<cfreturn local.replacedColdSpringXML />
	</cffunction>
	
	<cffunction name="replaceDynamicValues" access="private" returntype="string" output="false" hint="I replace any dynamic values in the ColdSpring XML with matching values in the specified value structure">
		<cfargument name="coldSpringXMLPath" type="string" required="true" hint="Path to ColdSpring XML File i.e. '/myapp/config/coldspring.xml'" />
		<cfargument name="dynamicValues" type="struct" required="false" default="#StructNew()#" hint="Structure containing the dynamic properties to be replaced. The key names must match the dynamic properties in the XML file." />
		<cfset var local = StructNew() />
		<cffile action="read" file="#arguments.coldSpringXMLPath#" variable="local.coldSpringXML" />
		<cfset local.coldSpringXML = ReReplaceNoCase(local.coldSpringXML, '.*<beans[^>]*>', '', 'all') />
		<cfset local.coldSpringXML = ReReplaceNoCase(local.coldSpringXML, '</beans>.*', '', 'all') />
		<cfset local.matches = ReMatchNoCase('\$\{[^}]*\}', local.coldSpringXML) />
		<cfset local.stringBuilder = CreateObject("java","java.lang.StringBuilder").init(JavaCast("string", local.coldSpringXML)) />
		<cfoutput>
		<cfloop from="1" to="#ArrayLen(local.matches)#" index="local.thisMatch">
			<cfset local.tempString = Mid(local.matches[local.thisMatch], 3, Len(local.matches[local.thisMatch]) - 3) />
			<cfif StructKeyExists(arguments.dynamicValues, local.tempString)>
				<cfset replaceValue(local.stringBuilder, local.matches[local.thisMatch], arguments.dynamicValues[local.tempString]) />
			</cfif>
		</cfloop>
		</cfoutput>
		<cfreturn local.stringBuilder.toString() />
	</cffunction>
	
	<cffunction name="replaceValue" access="private" returntype="void" output="false" hint="I recursively replace the specified dynamic property in the XML.">
		<cfargument name="stringBuffer" type="any" required="true" />
		<cfargument name="targetString" type="string" required="true" />
		<cfargument name="replacementValue" type="string" required="true" />
		<cfargument name="startPosition" type="numeric" required="false" default="0" />
		<cfset var local = StructNew() />
		<cfset local.stringIndex = arguments.stringBuffer.indexOf(JavaCast("string", arguments.targetString), JavaCast("int", arguments.startPosition)) />
		<cfif local.stringIndex neq -1>
			<cfset arguments.stringBuffer.replace(JavaCast("int", local.stringIndex), JavaCast("int", local.stringIndex + Len(arguments.targetString)), JavaCast("string", arguments.replacementValue)) />
			<cfset replaceValue(arguments.stringBuffer, arguments.targetString, arguments.replacementValue, local.stringIndex + Len(arguments.targetString)) />
		</cfif>
	</cffunction>
				
</cfcomponent>

		

Pretty neat if I do say so myself! And it's great to see how much more we can do with ColdSpring once we start thinking about actually extending it and adding useful behavior on to it.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner