Creating Thread-Safe Components With OnMissingMethod()

<cfcomponent
	output="false"
	hint="Creates a more thread-safe version of any CFC.">
 
	<!---
		Run pesudo code to set up default sturctures
		and data values.
	--->
	<cfset VARIABLES.Instance = {} />
	<cfset VARIABLES.Instance.Target = "" />
	<cfset VARIABLES.Instance.ID = CreateUUID() />
 
 
	<cffunction
		name="Init"
		access="public"
		returntype="any"
		output="false"
		hint="Returns an intialized thread-safe component.">
 
		<!--- Define arguments. --->
		<cfargument
			name="Target"
			type="any"
			required="true"
			hint="The CFC intance that we want to make thread safe"
			/>
 
		<!--- Store target. --->
		<cfset VARIABLES.Instance.Target = ARGUMENTS.Target />
 
		<!--- Return This reference. --->
		<cfreturn THIS />
	</cffunction>
 
 
	<cffunction
		name="OnMissingMethod"
		access="public"
		returntype="any"
		output="false"
		hint="Wraps around the target object to make the methods thread-safe.">
 
		<!--- Define arguments. --->
		<cfargument
			name="MissingMethodName"
			type="string"
			required="true"
			hint="The name of the missing method."
			/>
 
		<cfargument
			name="MissingMethodArguments"
			type="struct"
			required="true"
			hint="The arguments that were passed to the missing method. This might be a named argument set or a numerically indexed set."
			/>
 
		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />
 
 
		<!---
			Lock this method call. We are trying to make this
			method thread safe so give it a named lock that is
			a combination of the CFC ID and the method name.
		--->
		<cflock
			name="#VARIABLES.Instance.ID#-#ARGUMENTS.MissingMethodName#"
			type="exclusive"
			timeout="5">
 
 
			<!--- Get the array of argument keys. --->
			<cfset LOCAL.Keys = StructKeyArray(
				ARGUMENTS.MissingMethodArguments
				) />
 
 
			<!---
				Check to see if we have numeric argument keys
				(indicating that we have orderd arguments, NOT
				named arguments).
			--->
			<cfif (
				ArrayLen( LOCAL.Keys ) AND
				IsNumeric( LOCAL.Keys[ 1 ] )
				)>
 
				<!---
					With ordered arguments, we have to build up a
					dynamic evaluation string (this is lame, but
					I cannot find anyway to get around this).
				--->
				<cfset LOCAL.Args = "" />
 
				<!--- Loop over keys. --->
				<cfloop
					index="LOCAL.Index"
					array="#LOCAL.Keys#">
 
					<!--- Get the argument value. --->
					<cfset LOCAL.Value = ARGUMENTS.MissingMethodArguments[ LOCAL.Index ] />
 
					<!---
						Check to see if the value is a simple or
						complex value. All simple values will be
						quoted.
					--->
					<cfif IsSimpleValue( LOCAL.Value )>
 
						<!--- Quote the argument. --->
						<cfset LOCAL.Args &= (
							",""" &
							LOCAL.Value &
							""""
							) />
 
					<cfelse>
 
						<!---
							Send the complex value through without
							any quoting.
						--->
						<cfset LOCAL.Args &= (
							"," &
							LOCAL.Value
							) />
 
					</cfif>
 
				</cfloop>
 
 
				<!--- Remove the leading comma. --->
				<cfset LOCAL.Args = REReplace(
					LOCAL.Args,
					"^,",
					"",
					"one"
					) />
 
				<!--- Excute the dynamic method. --->
				<cfset LOCAL.Return = Evaluate(
					"VARIABLES.Instance.Target." &
					ARGUMENTS.MissingMethodName & "(" &
					LOCAL.Args &
					")"
					) />
 
			<!---
				If the method doesn't have ordered arguments,
				then we can simply pass the missing method
				arguments through as the arguments collection.
				This will be fine even if no arguments were
				passed through.
			--->
			<cfelse>
 
				<!---
					With named arguments, we can simply pass the
					missing method arguments directly onto the
					target instance using the argument colleciton.
				--->
				<cfinvoke
					component="#VARIABLES.Instance.Target#"
					method="#ARGUMENTS.MissingMethodName#"
					argumentcollection="#ARGUMENTS.MissingMethodArguments#"
					returnvariable="LOCAL.Return"
					/>
 
			</cfif>
 
 
			<!--- Return the resultant value. --->
			<cfreturn LOCAL.Return />
 
 
		</cflock>
	</cffunction>
 
</cfcomponent>

For Cut-and-Paste