Escape Keys - TomdeMan's Blog

The second release of TransferSync, didn't see any bug fixes. Instead, this edition includes some optimizations for those using Coldbox. There are no major changes to the methodology or function. Everything works the same. However, a few things have been updated to take advantage of the Coldbox cache and features to provide better performance.

Grab the latest version at RIAForge.org

Transfer has become almost a necessity for me these days. I recently had to deploy it on a clustered environment, and had to find a way to keep the Transfer cache between nodes in sync.

A centralized caching system like memcache would work, but a lot of refactoring would be required. Not to mention, Transfer's cache is its claim to fame.

I turned to google, and it led me to Sean Corfield's Blog. Where I found a number of good posts on the issue. Even some code that would do most of the dirty work for me. Score!

Almost...It didn't support composite keys. After I went at that, I decided to change the approach from requiring 2 gateways to just 1, and instead of 1 CFC to act as an observer and a gateway, I split them up.

The gateway gets set up in the CF Administrator. A config file is provided, and doesn't require any modification to get you going. Once it's running the JMS acknowledges it as a listener.

The observer gets attached to Transfer. It will trigger a method that sends a message to the JMS. The message carries the Transfer Object Class name and the ID of the Object that was either deleted or updated.

The JMS then brokers the message to all listening nodes.

That's right, there is a catch. It requires an instance of Active MQ JMS Message Broker, on each node That's an opensource Java Messaging Service by the Apache foundation. It's fairly light, taking up around 2MB RAM, and minimal CPU when at work. Not to mention, it's default installation will get you going instantly. The best part is the auto discovery that will detect other brokers on the same network. This frees you from having to maintain the config files for the CF gateway. No need to keep an up-to-date list of IPs for the nodes. You can deploy the gateway as-is to all nodes, and let ActiveMQ do it's thing.

The project is on RIAForge. I've included a sample app along with the code, for a couple reasons. If you are new to all this, then I suggest installing ActiveMQ, and using the sample app to verify everything is setup correctly. The other reason, is to provide sample code to speed up integration.

Quick Fix - Transfer and Reserverd Words

I ran in to this when I first started using Transfer ORM a while back, when I was building a new app. However, now I am curious for those working on an existing app and db structure.

If you have a table called User, for example, Transfer will throw an error. It generates the SQL without brackets [ ] and will usually indicate a syntax error. When really it's the reserved word USER that is throwing things off.

Originally, our team decided to rename the DB table to CoreUser and just map it in the XML to make the object look pretty. Again, this was for a new app we were building so it was early in the game and no additional refactoring was necessary at the time.

<object name="User" table="CoreUser">
<id name="UserId" type="GUID" column="UserId" generate="true" />

I did a quick Google search to see if any posts had mentioned another approach. Nothing new popped up, similar stories and struggles.

I happen to catch Mark online today and figured I'd run it by him. As always Mark hooked me up with a simple effective solution. So simple it made me feel stupid for not trying it.

Add the brackets to the table name in the transfer.xml file.

<object name="User" table="[User]">
   <id name="UserId" type="GUID" column="UserId" generate="true" />

Cake.

We know how to use the Coldbox environment interceptor to load different values for datasources. We know how to circumvent datasource.xml files with Transfer by using a datasource bean. Now, how can we use ColdSpring to put it all together for us.

<!-- coldbox -->
   <bean id="ColdboxFactory" class="coldbox.system.extras.ColdboxFactory" />
   <bean id="Coldbox" factory-bean="ColdBoxFactory" factory-method="getColdbox" singleton="true" />
   
   <!-- transfer -->
   <bean id="TransferFactory" class="transfer.TransferFactory" singeleton="true">
      <constructor-arg name="configuration">
         <bean class="model.TransferConfig">
            <constructor-arg name="DSNBean">
               <bean factory-bean="ColdBoxFactory" factory-method="getDatasource">
                  <constructor-arg name="alias">
                     <value>${Transfer_DSNAlias}</value>
                  </constructor-arg>
               </bean>
            </constructor-arg>
            <constructor-arg name="configPath">
             <value>${Transfer_ConfigPath}</value>
            </constructor-arg>
            <constructor-arg name="definitionPath">
             <value>${Transfer_DefinitionPath}</value>
            </constructor-arg>
         </bean>
       </constructor-arg>
   </bean>
      
   <bean id="Transfer" factory-bean="TransferFactory" factory-method="getTransfer" singleton="true" />
   <bean id="Datasource" factory-bean="TransferFactory" factory-method="getDatasource" singleton="true" />

That's what my coldspring.xml file would look like. There are a few things to note. I use Coldbox to get the Datasource information I need because it has already been defined in the Coldbox config or environment file. This is the reason I wanted to avoid a datasource.xml file for Transfer. Otherwise, I'd have the same DSN info repeated all over the place.

I also point to model.TransferConfig. A simple extension of the Transfer Configuration bean. You can learn more about that from my previous post.

If you notice, there is a Transfer_DSNAlias argument being passed into the Coldbox getDatasource method. This tells CB which DSN you want to load up and pass on to Transfer. Coldbox allows for multiple datasources to be stored for use in your App. But Transfer only connects to one at a time. So I made it a setting in the config/environment.

Here's a peak at my environements.xml file.

<environment name="development" urls="" patterns=".local">
   <Setting name="DebugMode"               value="true" />
   <Setting name="DebugPassword"               value="" />
   <Setting name="ReinitPassword"               value="" />
   <Setting name="EnableDumpVar"               value="true" />
   <Setting name="HandlersIndexAutoReload"          value="true" />
   <Setting name="ConfigAutoReload"            value="true" />
   <Setting name="HandlerCaching"               value="false" />
   <Setting name="EventCaching"               value="false" />
   
   <Setting name="Datasources" value="{'MyDSNAlias': {'Alias': 'MyDSNAlias' , 'Name': 'MyDSN', 'DBType': 'mssql', 'Username': 'dbusername', 'Password': 'dbpass'}}" />
   
   <Setting name="Transfer_ConfigPath" value="/config/transfer.xml.cfm" />
   <Setting name="Transfer_DefinitionPath" value="/model/definition" />
   <Setting name="Transfer_DSNAlias" value="MyDSNAlias" />
</environment>

That's it.

By default Transfer expects a path to a datasource.xml file. However, the TransferFactory.init() method will accept a configuration bean. Which you can find in the transfer.com.config path. The Configuration.cfc init() method is only looking for paths as arguments, and sets the DSN username and password to blank on creation. The object does have a hasDatasourceName() method, so how do we utilize this?

Well, I extended the object and overwrite the init() method. It now accepts a datasource bean and sets the DSN info in Transfer with it. When Transfer natively calls hasDatasourceName() it will bypass checking the datasourcePath.

<cfcomponent displayname="TransferConfig" hint="This is a TransferConfig Bean" output="false" extends="transfer.com.config.Configuration">
   
   <cffunction name="init" hint="Constructor" access="public" returntype="model.TransferConfig" output="false">
      <cfargument name="dsnBean"            type="any"      required="no"   default="" />
      <cfargument name="datasourcePath"      type="string"   required="no"   default="" />
      <cfargument name="configPath"         type="string"   required="no"   default="" />
      <cfargument name="definitionPath"      type="string"   required="no"   default="" />
      
      <cfscript>
         variables.instance = StructNew();
         
         setConfigPathCollection(ArrayNew(1));
   
         setDatasourceName(arguments.dsnBean.getName());
         setDatasourceUsername(arguments.dsnBean.getUsername());
         setDatasourcePassword(arguments.dsnBean.getPassword());

         setDataSourcePath(arguments.datasourcePath);
         setConfigPath(arguments.configPath);
         setDefinitionPath(arguments.definitionPath);
   
         return this;
      </cfscript>
   </cffunction>
   
</cfcomponent>

Postgre Error: Relation does not exist

I'm working on an app that integrates with the Transfer ORM. The code runs on an MS SQL and MySQL database without a problem. However, when switching over to Postgre one table causes things to flop.

When it queries the table i get the error: relation "tableName" does not exist.

I look at the SQL involved and its a basic select. So I try select * tableName, and get the same error in the query window.

If I add double quotes around "tableName" it works fine.

That's a solution but not an explanation for the problem.

Why is it, that for this tableName, I need the double quotes?

Well it can be one of two things...

The tableName is a reserved word in Postgre or when you created the table you used mixed cases (tableName) or upper case (TABLENAME). Had you created the table lower case (tablename) then you would have never seen this error. So if you are using an ORM that extends itself to Postgre be sure to create tables and column names in lower case.

Using a Decorator with Transfer

Transfer was built to allow you to extend its capabilities. Using the Decorator Pattern you can overlay/overwrite methods of a TransferObject and add methods of your own. Remember its a Decorator to the Transfer Object(the Bean). So the methods you add should be relative to the Bean. To add DB or other external functions, there are other ways.

Here's how you setup the Decorator.

1. Add the decorator attribute to the object element in your Transfer Config XML file.

<objectDefinitions>
<package name="Account">
<object name="Account" table="Account" decorator="model.account.accountDecorator" >
<id name="AccountId" type="UUID" generate="true"/>
<property name="Username" type="string" nullable="false" />
<property name="Password" type="string" nullable="false" />
<property name="FirstName" type="string" nullable="true" />
<property name="LastName" type="string" nullable="true" />
<property name="Email" type="string" nullable="false" />
<property name="UpdatedOn" type="date" nullable="false" />
<property name="CreatedOn" type="date" ignore-update="true" />
</object>
</package>
</objectDefinitions>

2. Create your Decorator CFC and extend the Transfer Decorator Object.

<cfcomponent displayname="accountDecorator" hint="Decorates Account TransferObject" output="false" extends="transfer.com.TransferDecorator">

<cffunction name="setPassword" access="public" returntype="void" output="false" hint="Replaces original setPassword to MD5 Hash the Password">
<cfargument name="password" type="string" required="true" />
<cfset getTransferObject().setPassword(hash(password)) />
</cffunction>

</cfcomponent>

You also have access to getTransfer() when you extend the Transfer Decorator Object.

That's it. Re-init Transfer and your Decorator methods will now be available from your Transfer Objects.

To take it a step further, I have created a Base Decorator to share methods accross Decorators. But that's another Blog.

Extending a Base Decorator with Transfer

So you have a few methods you want to include in every Transfer Object, and you don't want mess with mix-ins, and copy/paste isn't an option. You want to extend it but its already extending the transfer.com.TransferDecorator. So what can you do?

1. Create your Base Decorator CFC, and extend the transfer.com.TransferDecorator.

<cfcomponent displayname="baseDecorator" hint="This is the baseDecorator component" output="false" extends="transfer.com.TransferDecorator">

<cffunction name="listProperties" access="public" returntype="string" output="false" hint="Returns a List of the Properties">
<cfset var lReturn = '' />
<cfset var oIterator = getTransfer().getTransferMetaData(this.getClassName()).getPropertyIterator() />

<cfloop condition="#oIterator.hasNext()#">
<cfset lReturn = listAppend(lReturn,oIterator.next().getName()) />
</cfloop>

<cfreturn lReturn />
</cffunction>

<cffunction name="listColumns" access="public" returntype="string" output="false" hint="Returns a List of the Properties">
<cfset var lReturn = '' />
<cfset var oIterator = getTransfer().getTransferMetaData(this.getClassName()).getPropertyIterator() />

<cfloop condition="#oIterator.hasNext()#">
<cfset lReturn = listAppend(lReturn,oIterator.next().getColumn()) />
</cfloop>

<cfreturn lReturn />
</cffunction>

</cfcomponent>

2. Now change the 'extends' in the object decorator to extend the Base Decorator CFC

<cfcomponent displayname="accountDecorator" hint="This is the accountDecorator component" output="false" extends="model.baseDecorator">

<cffunction name="setPassword" access="public" returntype="void" output="false" hint="Replaces default setPassword method to MD5 Hash the Password">
<cfargument name="password" type="string" required="true" />
<cfset getTransferObject().setPassword(hash(password)) />
</cffunction>

</cfcomponent>

Now you'll have the baseDecorator and the accountDecorator methods available to you from your TransferObject.

Wait...What if you only need to extend the baseDecorator to your TransferObject and don't forsee a need for an accountDecorator? Dont create an empty accountDecorator just to extend the baseDecorator.

Change the value of the decorator attriute in the Transfer Config XML to the path of your baseDecorator.

<object name="Account" table="Account" decorator="model.baseDecorator" >