Escape Keys - TomdeMan's Blog

A number of requests and inquiries have come in about TransferSync and multiple application instances running on the same server.

Thanks goes to Dylan Miyake, who was kind enough to provide the solution.

I have updated the project TransferSync @ RIAforge, you can get the latest code there.

Major updates have been made to TransferSync.

This version has a critical fix for those dealing with related objects. Somehow, the afterCreate event was overlooked during the early conception of this approach for managing the Transfer cache using a JMS gateway.

Once, that was implemented, it was apparent that better synchronization for parent objects was needed. For instance, when a new child is created, or existing child has a parent switched during an update.

I have temporarily removed the sample app. Once I get a chance to clean it up, I'll bring it back. If you need samples, contact me.

Other then that, there have been some great improvements. Take a look at the change log, or grab the latest version here: TransferSync @ RIAForge

0.5 -CHANGE LOG- 3/9/2009
- enhancement - added observer toggle no longer dependent on lazy init
- enhancement - added debug toggle
- enhancement - added class filtering
- enhancement - added caching for parents
- enhancement - added getMemento and getVersion
- enhancement - more robust logging
- enhancement - coldbox versions supports pre-cf8
- fix - added support for after create events
- fix - added support for notifying parent classes

Here is a great post by Brian Ghidinelli on how to integrate TransferSync in a ModelGlue and Coldspring system.

Check it out: Synchronizing Transfer ORM in a Model-Glue/Coldspring cluster with TransferSync

A minor update with a major fix.

0.4 -CHANGE LOG- 11/13/2008 - enhancement - logging improvements and toggle setting/property added - enhancement - improved caching - enhancement - added getKeyMap method - enhancement - added argument to definition file - enhancement - changed definition file extension to .transfer - fix - added double lock to prevent concurrency issues with duplicate method creation

And for those using Coldbox there is an optimized version of the gateway in the package.

Grab a copy at: TransferSync @ riaForge

TransferSync v0.3 released.

If you don't know, TransferSync is a JMS gateway that keeps Transfer's cache synchronized across a cluster. It is delivered in a sample app to help you diagnose and get setup quickly.

I have just posted a quick update to the TransferSync project. There have been a couple fixes and some minor enhancement. Thanks to some great suggestions by Brian Ghidinelli.

Error trapping has been added if the gateway is down or an error occurs while sending a message. Additional properties have been added to the gateway class to make it more generic. Logging was updated to allow for better debugging, as well.

You can grab the latest version from TransferSync @ RIAForge.

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>

More Entries