-
Notifications
You must be signed in to change notification settings - Fork 24
Using ColdSpring With Mach II
- What is ColdSpring
- What is a Mach-II Property
- What is the ColdspringProperty
- ColdspringProperty Usage and Configuration
-
Parameter Descriptions
beanFactoryPropertyName
configFile
configGilePathIsRelative
resolveMachIIDependencies
parentBeanFactoryScope
parentBeanFactoryKey
placeFactoryInApplicationScope
placeFactoryInServerScope
autowireAttributeName
generateRemoteProxies
cfcGenerationLocation
dotPathToCfcGenerationLocation
beansToMachIIProperties
- Parent/Child Bean Factories Configuration for Use with Modules
- Using Mach-II Properties within ColdSpring
- Using Autowire by Depends Attribute
-
Common Errors Using the
ColdSpringProperty
- Additional Information and Considerations
- Closing Thoughts
ColdSpring is dependency injection CFML framework based on the Spring framework for Java. In short, it makes your development life a lot easier by abstracting your object dependencies into an easy to read XML configuration file, rather than having those dependencies sprinkled throughout your application.
The concepts behind ColdSpring seem intimidating (DI, IoC, setter injection, etc), but in all reality what it does is much easier to understand. In a nutshell, ColdSpring wires objects together. You defined relationships using an XML configuration file. When you ask ColdSpring for an object, it first checks a cache to see if it has one yet and if not uses this wiring plan to give you back a fully wired and instantiated object back.
The first hurdle to get over is one term that ColdSpring uses the term "bean". ColdSpring is a functionality port of a package in the Spring project (Java). In Spring, an object == bean while in CFML we typically use the term bean to describe a business object. So when using ColdSpring, when you see "bean" think "object" instead.
Let's look at a simple CS configuration file:
<beans>
<bean id="showroomService"
class="spectrum.model.showroom.showroomService">
<property name="showroomDao"><ref bean="showroomDao"/></property>
<property name="showroomGateway"><ref bean="showroomGateway"/></property>
<property name="udfs"><ref bean="udfs"/></property>
</bean>
<bean id="showroomDao"
class="spectrum.model.showroom.showroomDao"
parent="baseDaoGateway" />
<bean id="showroomGateway"
class="spectrum.model.showroom.showroomGateway"
parent="baseDaoGateway" />
</beans>
Each "bean" (object) in our model gets a definition. As you can see, the
ShowroomService
has some properties. This are items are need to be
injected into the service for it to work. Without ColdSpring in the
picture, you would probably create the ShowroomDao
and
ShowroomGateway
in the init()
method of the ShowroomService
.
Instead, the instantiation of these objects is taken care of by
ColdSpring and injected into the service by using setter methods. What I
just described is called Inversion of Control (IoC). Instead of letting
the service have "control" on how objects are instantiated, an outside
"provider" (ColdSpring) takes care of doing that and setting them into
the service.
This wiki entry does not explain ColdSpring in great detail or how to use the ColdSpring API. For more information, please go read more about ColdSpring at http://www.coldspringframework.org. This wiki entry details how to setup ColdSpring integration with Mach-II.
A property in Mach-II is a resource
defined in the mach-ii.xml
configuration file which is made available
on an application wide level. These properties can be simple name/value
pairs, and since Mach-II 1.5, xml described structs and arrays, and
(most importantly) full CFCs ready for use throughout your application.
Mach-II properties are instantiated during the initialization process of
the application.
Example Properties:
<properties>
<property name="myImagePath" value="path/to/my/images" />
<property name="debugMode" value="true" />
<property name="coldspringProperty" type="MachII.properties.ColdspringProperty">
<parameters>
<parameter name="configFile" value="config/coldspring.xml" />
</parameters>
</property>
</properties>
Notice first that when defining CFC properties a "type" attribute is
required which points to the location of the CFC file on disk. Notice
also that input parameters may be specified in the mach-ii.xml
file
and passed into CFC properties at the time of their instantiation. Since
CFC type properties often have a number of parameters that may be passed
in, it can be much cleaner to simply include the property via an include
statement, rather than within the main properties node of the
mach-ii.xml
configuration file. This would look like the following:
<includes>
<include file="./coldspringProperty.xml" />
<include file="./loggingProperty.xml" />
<include file="./environmentProperty.xml" />
</includes>
Then, you would simply edit the configuration of the
ColdspringProperty
in its own coldspringProperty.xml
file. We will
look at all the details of configuring the ColdspringProperty later in
this document.
Aside from being an awesome way to tie Mach-II and ColdSpring together,
there is really nothing magical about the ColdspringProperty
. It is a
Mach-II property like any other - it just happens to be pre-built for
you and distributed with the Mach-II framework to allow you to easily
wire up your Mach-II application to work with ColdSpring. Using the
ColdspringProperty
is a matter of including the
coldspringProperty.xml
file from within your main mach-ii.xml
config
file, then configuring the parameters in the coldspringProperty.xml
file at your leisure.
Let's now take a close look at the different options available to you
when configuring the ColdspringProperty
.
<property name="coldSpringProperty" type="MachII.properties.ColdspringProperty">
<parameters>
<!-- Mach-II property that reference ColdSpring beanFactory - Default: 'coldspring.beanfactory.root' -->
<parameter name="beanFactoryPropertyName" value="serviceFactory"/>
<!-- Path to the ColdSpring config file (required) -->
<parameter name="configFile" value="/path/to/services.xml"/>
<!-- Whether path is relative (mapped) or absolute - Default: FALSE -->
<parameter name="configFilePathIsRelative" value="true"/>
<!-- Whether to resolve dependencies for listeners/filters/plugins - Default: FALSE -->
<parameter name="resolveMachIIDependencies" value="false"/>
<!-- scope to pull in a parent bean factory into a child bean factory - Default: application -->
<parameter name="parentBeanFactoryScope" value="application"/>
<!-- key to pull in a parent bean factory from the application scope - Default: FALSE -->
<parameter name="parentBeanFactoryKey" value="serviceFactory"/>
<!-- Whether to place the bean factory in the application scope - Default: FALSE -->
<parameter name="placeFactoryInApplicationScope" value="false" />
<!-- Whether to place the bean factory in the server scope - Default: FALSE -->
<parameter name="placeFactoryInServerScope" value="false" />
<!-- Whether to automatically generate remote proxies for you - Default: FALSE -->
<parameter name="generateRemoteProxies" value="true" />
<!-- Autowire attribute name to introspect in cfcomponent tags - Default: 'depends' -->
<parameter name="autowireAttributeName" value="depends" />
<!-- Indicates where to write the temporary CFCs of the dynamic autowire method
generation feature. Specify a path that can be expanded via expandPath().
Default: to current location of ColdspringProperty.cfc
DO NOT DEFINE THESE PARAMETERS UNLESS YOU WANT TO OVERRIDE THE DEFAULT
<parameter name="cfcGenerationLocation" value="PathThatCanBeExpanded" />
-->
<!-- Indicates the dot path to where the temporary CFCs of the dynamic autowire method
generation feature.
DO NOT DEFINE THESE PARAMETERS UNLESS YOU WANT TO OVERRIDE THE DEFAULT
<parameter name="dotPathToCfcGenerationLocation" value="DotPathToCFCGenerationLocation" />
-->
<!-- Struct of bean names and corresponding Mach-II property
names for injecting back into Mach-II
Default: does nothing if struct is not defined -->
<parameter name="beansToMachIIProperties">
<struct>
<key name="ColdSpringBeanName1" value="MachIIPropertyName1" />
<key name="ColdSpringBeanName2" value="MachIIPropertyName2" />
</struct>
</parameter>
</parameters>
</property>
The beanFactoryPropertyName
parameter value is the name of the Mach-II
property name that will hold a reference to the ColdSpring !beanFactory.
This parameter defaults to coldspring.beanfactory.root
if not defined.
The configFile
parameter value holds the path of the ColdSpring
configuration file. The path can be an relative, CFML mapped or absolute
path. If you are using a relative or mapped path, be sure to set the
configFilePathIsRelative
parameter to TRUE or the ColdSpring will not
find your configuration file.
The configGilePathIsRelative
parameter value defines if the configure
file is a relative (including CFML mapped) or absolute path. If you are
using a relative or mapped path, be sure to set the
configFilePathIsRelative
parameter to TRUE or the property will not
find your configuration file.
- TRUE (for relative or mapped configuration file paths)
- FALSE (for absolute configuration file paths)
The resolveMachIIDependencies
parameter value indicates if the
property to "automagically" wire Mach-II
listeners/filters/plugins/properties. This parameter defaults to FALSE
if not defined.
- TRUE (resolves all Mach-II dependencies)
- FALSE (does not resolve Mach-II dependencies)
The parentBeanFactoryScope
parameter values defines which scope to
pull in a parent bean factory. This parameter defaults to 'false' if not
defined and indicates that a parent bean factory does not need to be
referenced.
The parentBeanFactoryKey
parameter values defines a key to pull in a
parent bean factory from the scope specified in the
parentBeanFactoryKey
parameter. This parameter defaults to 'false' if
not defined and indicates that a parent bean factory does not need to be
referenced.
The placeFactoryInApplicationScope
parameter indicates whether or not
to place the bean factory in the application scope. This parameter is
used to for setting your bean factory for use as a parent. The key that
used is driven from the value from of the beanFactoryPropertyName
parameter. If the parent uses the same value for the
beanFactoryPropertyName
, the module name (e.g. "_account") is append
to the end of the key to eliminate namespace conflicts in the
application scope. This parameter defaults to 'false' if not defined and
indicates that this bean factory should not be placed in the application
scope.
The placeFactoryInServerScope
parameter indicates whether or not to
place the bean factory in the server scope. This parameter is used to
for setting your bean factory for use as a parent. The key that used is
driven from the value from of the beanFactoryPropertyName
parameter.
If the parent uses the same value for the beanFactoryPropertyName
, the
module name (e.g. "_account") is append to the end of the key to
eliminate namespace conflicts in the server scope. This parameter
defaults to 'false' if not defined and indicates that this bean factory
should not be placed in the server scope.
The autowireAttributeName
parameters indicates the name of the
attribute to introspect for in cfcomponent tags when using the dynamic
autowire getter/setter method generation feature of the
ColdspringProperty
. Autowire method generation injection allows you to
put a list of ColdSpring bean names in the autowire attribute (which
default to 'depends') in cfcomponent tag of your listeners, filters,
plugins and properties CFC in Mach-II. ColdSpring property will
automatically generate and dynamically inject getters/setters for the
listed bean names into your target cfc at runtime. This does not modify
the contents of the cfc file, but injects dynamically while the cfc is
in memory. This feature allows you to stop having to type out
getters/setters for the service that you want ColdSpring to inject into
your cfc.
Example:
<cfcomponent extends="MachII.framework.Listener" depends="someService">
... additional code ...
</cfcomponent>
This will dynamically inject a getSomeService()
and setSomeService()
method into this listener. ColdSpring will then use the bean name and
use setter injection to inject the bean into the listener.
Automatically generates or regenerates remote proxies of the type
coldspring.aop.framework.RemoteFactoryBean
that are created by
ColdSpring when the CS bean factory is instantiated. By default,
ColdSpring creates the remote proxies once and caches them to disk. The
only way to get Coldspring to regenerate the
remote proxies is to delete the remote proxies on disk or use the API.
Set this parameter to true to have this done for you automatically. This
parameter was added in Mach-II 1.6.
This parameter was removed in Mach-II Simplicity 1.8 as this implementation was replaced with a faster option.
The cfcGenerationLocation
parameter indicates where to write the
temporary CFCs of the dynamic autowire method generation feature.
Specify a path that can be expanded via expandPath(). Defaults to
current location of the ColdspringProperty.cfc
.
DO NOT DEFINE THESE PARAMETERS UNLESS YOU WANT TO OVERRIDE THE DEFAULT.
This parameter was removed in Mach-II Simplicity 1.8 as this implementation was replaced with a faster option.
The dotPathToCfcGenerationLocation
parameter indicates the dot path to
where temporary CFCs are written for the dynamic autowire method
generation feature.
DO NOT DEFINE THESE PARAMETERS UNLESS YOU WANT TO OVERRIDE THE DEFAULT.
The beansToMachIIProperties
parameter holds a struct of bean names and
corresponding Mach-II property names. This parameter will inject the
specified beans in the Mach-II property manager as the bean factory has
been loaded. In the past, a separate property has to be written to
accomplish this task. This should be used for framework required
"utility" objects that you want to be managed by ColdSpring such as UDF,
i18n or session facade objects. Do not use this feature to inject your
model objects into the Mach-II property manager.
<property name="coldspringProperty" type="MachII.properties.ColdspringProperty">
<parameters>
<parameter name="configFile" value="/path/to/config/services.xml" />
<parameter name="beanFactoryPropertyName" value="serviceFactory"/>
<parameter name="autowireAttributeName" value="depends" />
<parameter name="placeFactoryInApplicationScope" value="true"/>
<parameter name="resolveMachIIDependencies" value="true"/>
<parameter name="beansToMachIIProperties">
<struct>
<key name="NameOfColdSpringBean" value="PropertyName" />
</struct>
</parameter>
</parameters>
</property>
Base Mach-II Config File (i.e. Parent Factory):
<property name="ColdSpring" type="MachII.properties.ColdspringProperty">
<parameters>
<parameter name="beanFactoryPropertyName" value="serviceFactory"/>
<parameter name="configFile" value="/path/to/config/services.xml"/>
<parameter name="configFilePathIsRelative" value="true"/>
<parameter name="placeFactoryInApplicationScope" value="true"/>
<parameter name="resolveMachIIDependencies" value="true"/>
</parameters>
</property>
You must put the parent bean factory in the application (or server
scope) in orderfor a module to inherit from a parent factory. This
example put the parent factory into the application.serviceFactory
variable.
Account Module Config File (i.e. Child Factory):
<property name="ColdSpring" type="MachII.properties.ColdspringProperty">
<parameters>
<parameter name="beanFactoryPropertyName" value="serviceFactory"/>
<parameter name="configFile" value="/path/to/modules/account/config/services_account.xml"/>
<parameter name="configFilePathIsRelative" value="true"/>
<parameter name="resolveMachIIDependencies" value="true"/>
<parameter name="placeFactoryInApplicationScope" value="true"/>
<parameter name="parentBeanFactoryScope" value="application"/>
<parameter name="parentBeanFactoryKey" value="serviceFactory"/>
</parameters>
</property>
You are NOT required to put child factories into the application (or
server scope) for modules to inherit froma a parent factory. However, in
this example the account module puts this child factory into the
application scope. Since the parent and module use the same
beanFactoryPropertyName
, an application scope namespace conflict would
occur - so the Property appends the module name to the end. This factory
would be located in application.serviceFactory_account
variable.
By default, the ColdSpringProperty
passes ColdSpring a structure
called defaultProperties
. This is the term used by ColdSpring, but the
defaultProperties
is a reference to the Mach-II properties which
you then can use as bean contructor-args or properties when defining
your beans.
Using the default properties in ColdSpring is easy by using the
${nameOfDefaultProperty}
syntax in your XML file.
<bean id="addressGateway"
class="lightpost.model.address.address.addressGateway_mysql">
<constructor-arg name="dbDsn"><value>${dbDsn}</value></constructor-arg>
<constructor-arg name="dbTimeout"><value>${dbTimeout}</value></constructor-arg>
</bean>
The ${dbDsn}
and ${dbTimeout}
values in the example match properties
that were defined in the Mach-II properties section with the same name.
The ${}
is telling ColdSpring to do a substitution at runtime with the
value passed into the bean container from the Mach-II properties. And if
you need to pass in structures or arrays previously defined as machii
properties, update your Coldspring config with:
<bean id="SomeCFC" class="model.com.SomeCFC">
<property name="someProperty">
<map>${someStruct}</map>
</property>
<property name="anotherProperty"
<list>${someArray}</list>
</property>
</bean>
The autowire by dynamic method generation feature eliminates the necessity of creating get/set methods or constructor variables in all Mach-II developer extended CFCs:
- Listeners
- Plugins
- Filters
- Properties
- Endpoints (1.9+)
Before Mach-II Simplicity (1.8), the ColdSpringProperty
would autowire
in your beans by looking for setter methods in your listeners, filters,
properties and plugins:
<cffunction name="setSomeService" access="public" returntype="void" output="false"
<cfargument name="someService" type="component" required="true"/>
<cfset variables.someService = arguments.someService />
</cffunction>
<cffunction name="getSomeService" access="public" returntype="component" output="false"
<cfreturn variables.someService />
</cffunction>
Writing these tedious methods are no longer necessary because the new
MachII.properties.ColdSpringProperty
provides easy ColdSpring
integration with Mach-II applications by autowiring these objects using
the autowire attribute name (by default named depends
) in cfcomponent
tags. This allows you to autowire ColdSpring managed beans into your
listeners, filters, plugins and property CFCs by defining the bean name
as metadata in your cfcomponent tag without having to write concrete
getter/setter methods.
N.B. The depends
attribute only works with components that extend a
Mach-II component and will not work on non-Mach-II components.
The following example shows the parameter that must be included in the
ColdSpringProperty
declaration to set the attribute name so
ColdSpring
can inject the dependencies.
Define attribute name:
<parameter name="autowireAttributeName" value="depends" />
If the parameter is not included, the ColdspringProperty
will use the
default attribute name 'depends'.
Sample ColdSpringProperty
definition:
<property name="ColdSpring" type="machii.properties.ColdspringProperty">
<parameters>
<parameter name="beanFactoryPropertyName" value="serviceFactory"/>
<parameter name="configFile" value="services.xml"/>
<parameter name="configFilePathIsRelative" value="true"/>
<parameter name="placeFactoryInApplicationScope" value="true"/>
<parameter name="resolveMachIIDependencies" value="true"/>
<parameter name="autowireAttributeName" value="depends" />
</parameters>
</property>
Using autowire by dynamic method generation, it allows you to take your listener from this:
<cfcomponent extends="MachII.framework.Listener">
<cffunction name="setSomeService" access="public" returntype="void" output="false"
<cfargument name="someService" type="component" required="true"/>
<cfset variables.someService = arguments.someService />
</cffunction>
<cffunction name="getSomeService" access="public" returntype="component" output="false"
<cfreturn variables.someService />
</cffunction>
<cffunction name="setOtherService" access="public" returntype="void" output="false"
<cfargument name="otherService" type="component" required="true"/>
<cfset variables.otherService = arguments.otherService />
</cffunction>
<cffunction name="getOtherService" access="public" returntype="component" output="false"
<cfreturn variables.otherService />
</cffunction>
<cffunction name="doSomethingCool" access="public" returntype="void" output="false"
hint="Does something very cool">
<cfargument name="event" type="MachII.framework.Event" required="true"/>
<cfset getSomeService().doSomethingCool(arguments.event.getArg("tooCoolForSchool")) />
</cffunction>
</cfcomponent>
To this using a custom autowire attribute name using depends
as the
metadata attribute (since this is customizable you could set the
autowireAttributeName
parameter above to cs
and use that as the
metadata attribute):
<cfcomponent extends="MachII.framework.Listener" depends="someService,otherService">
<cffunction name="doSomethingCool" access="public" returntype="void" output="false"
hint="Does something very cool">
<cfargument name="event" type="MachII.framework.Event" required="true"/>
<cfset getSomeService().doSomethingCool(arguments.event.getArg("tooCoolForSchool")) />
</cffunction>
</cfcomponent>
As you can see, the depends
attribute is indicating to the
ColdspringProperty
to generate getter/setter methods for beans named
someService
and otherService
and then autowire those beans into the
listener. Even though the getSomeService()
method is not physically
present in your Listener source code, the method will still be available
for use as ColdspringPropery
will create and dynamically inject the
method in memory into your listener. The someService
injected
component is also available in the variables.someService
variable.
For each autowired component, the ColdspringProperty
will generate,
inject and autowire in the following methods for each component in the
depends
metadata attribute.
Method Name or Variable | Description |
---|---|
getXYZ() | Getter for XYZ component |
setXYZ() | Setter for XYZ component. Used by the ColdspringProperty to autowire the component into the target CFC. |
variables.xyz | The variables namespace used by the getter and setter that stores the autowired component in the target CFC. |
You must use the
MachII.properties.ColdspringProperty
component in order to enable the new dynamic method generation autowiring. This feature is not available in the olderColdspringPlugin
orColdspringProperty
that is bundled in the ColdSpring core. Not using the property bundled in the Mach-II core is the most common error when trying to implement this feature in a Mach-II application.
The ColdspringProperty
needs write permissions to the
MachII.properties
directory if you are using the autowire by depends
attribute. The location of this directory would be wherever you have
Mach-II installed. The property needs write permissions because it
dynamically builds a CFC with the required methods so it can inject them
via mixins to the target CFC. Currently, there is no way to dynamically
build a CFC in memory and instantiate it. This requires us to write
temporarily write it to disk first, instantiate it, and then delete the
temporary file. If your CFML server does not have write permissions, it
will throw an error similar to this:
An error occurred when performing a file operation write
on file /path/to/MachII/properties/79CA8568FB6F49E4384F1F41E3A289F7.cfc.
Giving your CFML server write permissions to that directory will fix this issue.
Some deployments of CFML engines seem to mess up the temporary CFC generation location. You might get an exception like this:
Please check that the dot path location '9B11663F57F7D926B0B63CEB348C0847'
and cfcGenerationLocation 'E:\JRun4\servers\Q5CF9\cfusion.ear\cfusion.war'
point to the same directory.
Luckily the temporary CFC generation location is configurable.
This parameter was removed in Mach-II Simplicity 1.8 as this implementation was replaced with a faster option.
The cfcGenerationLocation
parameter indicates where to write the
temporary CFCs of the dynamic autowire method generation feature.
Specify a path that can be expanded via expandPath(). Defaults to
current location of ColdspringProperty.cfc
.
DO NOT DEFINE THESE PARAMETERS UNLESS YOU WANT TO OVERRIDE THE DEFAULT
This parameter was removed in Mach-II Simplicity 1.8 as this implementation was replaced with a faster option.
The dotPathToCfcGenerationLocation
parameter indicates the dot path to
where temporary CFCs are written for the dynamic autowire method
generation feature.
DO NOT DEFINE THESE PARAMETERS UNLESS YOU WANT TO OVERRIDE THE DEFAULT
If you have a mapping to your application, you could create a /temp
folder to generation a CFC in.
There have been some reports from people who have migrated from an older
version of the ColdSpring connector (files like
coldspring.machii.ColdspringProperty
or
coldspring.machii.ColdspringPlugin
) having problems even when
reloading their application. Some possible solutions are:
- Restart to your CFML server service
- Clear your CFML template cache (located in your CFML server administrator)
Another common error is ColdspringProperty
throws an error saying the
it cannot find a method named
findImports().
- The
ColdspringProperty
does not walk the inheritance tree. If your listener, plugin, filter or property extends something other thanMachII.framework.XXX
where XXX is the normal base component for that framework extension, the property will not walk the inheritance tree and check the parent object fordepends
or setters to autowire in. A feature enhancement was filed for this on ticket (trac-wiki) #86 "enhancement: Walking Inheritance Tree in Listeners/Plugins (closed: Moved to GitHub)"). - This new feature requires ColdSpring 1.2 Stable or higher (such as bleeding edge release from a source code repository).
As a developer of Mach-II applications, you are certainly not required to use the ColdspringProperty. It is simply a convenient way to "wire up" Mach-II and ColdSpring so that you can focus on your custom development rather than configuration and setup. The ColdspringProperty does not in any way change the backwards compatibility of the framework.