Archive for the ‘ recommended ’ Category

I’ve had a task I assigned to myself open for some time now. I needed to figure out how to implement Flex/ColdFusion messaging for our clients. There were three (3) criteria for success:

  1. It needed to integrate seamlessly with our existing Cairngorm Extensions
  2. It needed to allow for application level messages
  3. It needed to allow for client specific messages

The trick was to create an abstract class that multiple departments could use, because internally we use ColdFusion, Java and .NET to produce and consume messages. In our department, we use ColdFusion, so I had to create a concrete implementation of the abstract class called ColdFusionMessagingDelegate which handles the construction and publication of an AsyncMessage to a ColdFusion Event Gateway.

Other departments’ implementation of this process will be different, so I couldn’t force an implementation in the abstract class.

Additionally, in the application I was using for testing, I wanted to have two categories of messages:

  1. Messages intended to be produced and consumed by individual clients
  2. Messages intended to be consumed by any client

To that end, I created another abstract class at the application level that simply passed along specific a Flex Producer and Consumer for each category. I then created two concrete classes. One specified the Producer and Consumer for the application, and the other specified the Producer and Consumer for that individual client.

Here’s a diagram showing the architecture.

Generic Messaging Architecture

Generic Messaging Architecture

So far, it’s working great in the development test lab, but I still need to get with my colleagues for a group code review and hopefully fine tune the code and the architecture a bit more.

1   Preface

At work, we’re investigating using ColdFusion Event Gateways to handle certain complex types of communication between our Flex user interfaces and ColdFusion, so yesterday I jumped on Google and started researching and ran into some roadblocks. However, after some monkeying around and more poring over of documentation and articles, I finally got a basic example working.

As usual, now that I’ve discovered how to set up a somewhat advanced feature of a platform, I’m sharing the knowledge in a comprehensive article. The documentation available for getting ColdFusion and Flex to communicate via event gateways was scattered, incomplete, or incomprehensible. In many of the articles, there were a lot of words, but little or no code, which does not help developers at all. Don’t tell me how it works, SHOW me how it works.

2   Creating a ColdFusion Listener Component

The listener component is the ColdFusion code that will receive any message sent from your Flex client via the DataServicesMessaging gateway type in your ColdFusion admin (more on that later). The basic structure is simple – just need one method named onIncomingMessage() in your component.

Create a file called InterceptFlex.cfc somewhere in your webroot and put the following code in it.

InterceptFlex.cfc

<cfcomponent output="false">
   <cffunction name="onIncomingMessage" returntype="any">
         <cfargument name="event" type="struct" required="true" />

	</cffunction>
</cfcomponent>

3   Setting Up Your Event Gateway

Now you need to go to your ColdFusion Administrator so that you can create an event gateway that specifies that any message coming in on a certain destination will be sent to the InterceptFlex component.

3.1   Destinations

There is a destination already set up for you in the \WEB-INF\flex\services-config.xml file (or \WEB-INF\flex\messaging-config.xml file if you are running BlazeDS) called ColdFusionGateway, and we’re just going to use that for this example.

You just need to make one change for this sample application to work. Open up the XML file and find the ColdFusionGateway destination. In the <properties> block add the following server property:

<properties>
   <server>
      <durable>false</durable>
      <allow-subtopics>true</allow-subtopics>
   </server>

   ...
</properties>

3.2   Gateway Setup

Now, go to the Gateway Instances section of your ColdFusion Administrator and start creating a gateway.

  1. The gateway ID can be anything, and you can just call it “test” for now.
  2. Select DataServicesMessaging as the type.
  3. The CFC path is the physical path to your InterceptFlex.cfc file you created.
  4. the configuration file is located in the \WEB-INF\cfusion\gateway\config\ directory, and you can use the one already set up named flex-messaging-gateway.cfg. So find that file and enter the path here.
  5. Click the Add Gateway Instance button.
  6. Start the gateway instance you just created.

4   Sending Messages From Flex

This application is a slight modification from the code provided in the Adobe article Introduction to the Flex Message Service, which I highly recommend you read if you’re new to Flex Messaging.

Create a new Flex application called Chat and paste this code into the Chat.mxml file. This code will connect to the ColdFusionGateway destination and send the message object to ColdFusion. ColdFusion, in turn, uses the event gateway instance we set up to send that object to the onIncomingMessage() method of the InterceptFlex.cfc component.

<?xml version="1.0" encoding="iso-8859-1"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*"
    pageTitle="Simple Flex Chat" creationComplete="createChat()">

    <mx:Script>
        <![CDATA[

        import mx.messaging.Consumer;
        import mx.messaging.Producer;
        import mx.messaging.events.MessageAckEvent;
        import mx.messaging.events.MessageFaultEvent;
        import mx.messaging.events.MessageEvent;
        import mx.messaging.messages.AcknowledgeMessage;
        import mx.messaging.messages.AsyncMessage;
        import mx.utils.ObjectUtil;

        private var chatPublisher:Producer;
        private var chatSubscriber:Consumer;

        private function createChat():void
        {
            chatPublisher = new Producer();
            chatPublisher.addEventListener(MessageFaultEvent.FAULT, onProducerFault);
            chatPublisher.destination = "ColdFusionGateway";

            chatSubscriber = new Consumer();
            chatSubscriber.addEventListener(MessageAckEvent.ACKNOWLEDGE, onConsumerAck);
            chatSubscriber.addEventListener(MessageEvent.MESSAGE, receiveChatMessage);
            chatSubscriber.destination = "ColdFusionGateway";
            chatSubscriber.subtopic = "simpleChat";
            chatSubscriber.subscribe();
        }

        private function onProducerFault(faultEvent:MessageFaultEvent):void
        {
            output.text += "[Error: " + faultEvent.message.toString() + "]\n";
        }

        private function onConsumerAck(ackEvent:MessageAckEvent):void
        {
            output.text += "[Got ack for subscribe or unsubscribe operation]\n";
        }

        // Note: these are the event-handling methods from above, now moved into the separate source file:

        private function sendChatMessage():void
        {
            var msg:AsyncMessage = new AsyncMessage();
            msg.body = input.text;
            msg.headers["username"] = "Me";
            msg.headers["gatewayid"] = "test";
            chatPublisher.subtopic = "simpleChat";
            chatPublisher.send(msg);
            input.text = "";
        }

        private function receiveChatMessage(msgEvent:MessageEvent):void
        {
            var msg:AsyncMessage = AsyncMessage(msgEvent.message);
            output.text += msg.headers["username"] + ": " + msg.body + "\n";
        }
        ]]>
    </mx:Script>

    
    <mx:Panel title="Simple Flex Chat">
        <mx:TextArea id="output" width="500" height="220" />
        <mx:TextInput id="input" width="500" enter="sendChatMessage()" />
        <mx:ControlBar horizontalAlign="center" width="500">
            <mx:Button id="clearBtn" label="Clear" click="output.text =''" />
        </mx:ControlBar>
    </mx:Panel>

</mx:Application>

5   Respond to the Flex Client

Now that ColdFusion has received the message, we’re simply going to respond back. You can enhance this to do anything you wish – send out emails, update database tables, log messages – but for now we’re just going to send a message back that let’s the Flex client know that the mechanism works.

<cfcomponent output="false">
   <cffunction name="onIncomingMessage" returntype="any">
      <cfargument name="event" type="struct" required="true" />

      <cfscript>
      x = structNew();
      x.body = "hello right back at ya";
      x.destination = "ColdFusionGateway";
      x.headers = structNew();
      x.headers['username'] = "System";
      x.headers['DSSubtopic'] = "simpleChat";
      x.lowercasekeys = "yes";
      </cfscript>

      <cfreturn x />
   </cffunction>
</cfcomponent>

One of those esoteric details that took me forever to find out was how to apply a subtopic to a message in ColdFusion. In Flex, it’s absurdly simply, you simply say message.subtopic = “myTopic”;, but in ColdFusion, you have to set a header key named DSSubtopic with the topic name (see above).

6   Conclusion

To be safe, restart your ColdFusion instance, then fire up the simple Flex chat app and type in a message. After a few seconds, you will receive a message from System in the history box.

Cairngorm: Abstract Events Updated

Purpose

The intended purpose for the AbstractEvent class in Cairngorm was three-fold:

  1. Reduce the number of files needed to manage business events by organizing Events into domains
  2. Implement a callback feature so that a view can specify another action to perform upon the successful completion of an event
  3. Make each domain event class as lightweight as possible

Related Posts

Abstract Commands | Abstract Delegates

Concept

To accomplish these goals, each domain event (e.g. ProjectEvent, PaymentEvent, etc.) would be polymorphic (kinda) in that there is no longer just one identifier designated with the EVENT_ID constant. In addition, I wanted to abstract as much functionality and classification as possible into a common class that each domain event would extend.
abstract_event

Lightweight, Polymorphic Event Class

Here’s an example of a domain event. It has three public identifiers – CREATE_PROJECT, LOAD_PROJECTS, DEACTIVATE_PROJECT- and has no logic other than to pass its constructor’s arguments to AbstractEvent.

ProjectEvent.as

package business.events
{
   import com.adobe.cairngorm.control.CairngormEvent;

   public class ProjectEvent extends AbstractEvent
   {
      public static const CREATE_PROJECT:String = "createProject";
      public static const LOAD_PROJECTS:String = "getProjects";
      public static const DEACTIVATE_PROJECT:String = "deactivateProject";

      public function ProjectEvent(type:String, data:Object = null, callbacks:Array=null)
      {
         super(type, data, callback);
      }
   }
}

AbstractEvent Class

The AbstractEvent class itself isn’t much more complicated at all. It simply has three public variables to hold the type of the event, an object containing key/value pairs for the data, and the reference to the callback function in the view (if specified).

AbstractEvent.as

package business.events
{
   import com.adobe.cairngorm.control.CairngormEvent;

   public class AbstractEvent extends CairngormEvent
   {
      public var callbacks:Array = null;

      public function AbstractEvent(type:String, data:Object = null, callbacks:Array=[])
      {
         super(type);

         this.data = data;
         this.callbacks = callbacks;
      }
   }
}

Example View – Projects

Here’s some snippets of code from my project view. One piece of functionality is for a user to disable a selected project from a list in an AdvancedDataGrid. When the user clicks on the “Disable Project” button, the btnDeactivateProject_Click() function fires and creates a new Project event in which the loadProjects() function is specified as the callback function.

Projects.mxml

private function btnDeactivateProject_Click(e:Event):void
{
   var thisProject:Project = gridProjects.selectedItem as Project;
   new ProjectEvent(ProjectEvent.DEACTIVATE_PROJECT,
            {project:thisProject},
            [loadProjects]).dispatch();
}

public function loadProjects():void
{
   new ProjectEvent(ProjectEvent.LOAD_PROJECTS).dispatch();
}

<mx:Button id="btnDeactivateProject"
	click="btnDeactivateProject_Click(event)"
	x="10" y="393"
	label="Deactivate Project"/>

Mapping Events to Commands

Your controller logic doesn’t change. The only difference is that you’re using one domain event, with specified identities, mapped to different Commands.

Controller.as

package business
{
   import business.commands.*;
   import business.events.*;
   import com.adobe.cairngorm.control.FrontController;

   public class Controller extends FrontController
   {
      public function Controller()
      {
         super();
         addCommand(ProjectEvent.LOAD_PROJECTS, LoadProjectsCommand);
         addCommand(ProjectEvent.CREATE_PROJECT, CreateProjectCommand);
         addCommand(ProjectEvent.DEACTIVATE_PROJECT, DeactivateProjectCommand);
      }
   }
}

Cairngorm: Abstract Commands Updated

Purpose

The intended purpose for the AbstractCommand class in Cairngorm is three-fold:

  1. Further implementation of a callback feature – in conjunction with the AbstractEvent – so that a view can specify other actions to perform upon the successful completion of business event
  2. Create a Responder object for the Delegate to use
  3. Make each Command class as lightweight as possible

Related Posts

Batch Commands | Abstract Events | Abstract Delegates

Concept

abstract_command

Standard, Responseful Command

Below is an example Command in my system, it extends AbstractCommand which in turn implements the ICustomCommand interface. To make the Command responseful, an array of functions is passed from the Event to the Command, which will execute them upon success (see more below). Here’s an example call:

new EmployeeEvent(EmployeeEvent.GET_DETAILS,
         {employee_id:params.empID},
         [showEmployeeDetails, updateEmployeeGraph])

To implement a very basic command, it is only required to set the delegate property to a new instance of the appropriate Delegate class, and then invoke the needed method. The AbstractCommand will automatically create a new Responder object and assign it to the delegate.

LoadProjectCommand.as

public class LoadProjectCommand extends AbstractCommand
{
   private var _model:ModelLocator = ModelLocator.getInstance();

   override public function execute():void
   {
      delegate = new ProjectDelegate();
      (delegate as ProjectDelegate).loadProject(invoker.data);
   }

   override public function commandSuccess(event:Event):void
   {
      if (event.result.success)
      {
         _model.project = (event as ResultEvent).result.data;
         notifyInvoker();
      }
   }
}
}

The invoker object is a reference to the original Event that was called, and to get it assigned as a property of the Command, I extended the FrontController class so that it is assigned before the Command is executed.

protected function executeAbstractCommand(event:AbstractEvent):void
{
   var commandRef:Class = getCommand(event.type);
   var commandToExecute:ICustomCommand = new commandRef();

   commandToExecute.invoker = event;
   commandToExecute.execute();
}

AbstractCommand Class

This class is straightforward and handles the tedious aspects of creating a Command. The notifyInvoker() method simply loops through the array of callback functions – if they exist – and runs them.

AbstractCommand.as

public class AbstractCommand
{
   private var _callbacks:Array = null;
   private var _invoker:AbstractEvent = null;
   private var _delegate:AbstractDelegate = null;

   public function AbstractCommand() { }
   public function execute():void { }
   public function commandSuccess(event:Event):void { }
   public function commandFault(event:Event):void { }

   public function set delegate(d:*):void
   {
      _delegate = d;
      _delegate.setResponder(new Responder(commandSuccess, commandFault));
   }

   public function get delegate():*
   {
      return _delegate;
   }

   public function set invoker(e:AbstractEvent):void
   {
      _invoker = e;
      if (e.callbacks != null) _callbacks = e.callbacks;
   }

   public function get invoker():AbstractEvent
   {
      return _invoker;
   }

   public function notifyInvoker(info:Object = null):void
   {
      if (_callbacks != null)
      {
         for (var i:uint = 0; i < _callbacks.length; i++)
         {
            var callback:Function = _callbacks[i];
           (info != null) ? callback.call(this, info) : callback.call(this);
         }
      }
   }
}

ICustomCommand Interface

ICustomCommand.as

package business.commands
{
   import business.events.AbstractEvent;
   import flash.events.Event;

   public interface ICustomCommand
   {
      function commandSuccess(event:Event):void;
      function commandFault(event:Event):void;
      function execute():void;
      function set invoker(event:AbstractEvent):void;
      function set delegate(delegate:*):void;
   }
}

Cairngorm Patterns: Batch Commands

Related Posts

Abstract Commands

Basic Cairngorm Commands

In the base implementation of the Cairngorm micro-architecture, events and commands are mapped on a 1-1 basis. For every invoker (event) you have to specify one command in your controller.

public function Controller()
{
   super();
   addCommand(TeammateEvent.LOAD_TEAMMATES, LoadTeammatesCommand);
   addCommand(EffortEvent.LOAD_TYPES, LoadEffortTypesCommand);
   addCommand(ProjectEvent.LOAD_PROJECTS, LoadProjectsCommand);
   addCommand(TeammateEvent.LOAD_TEAMMATE_ROLES, LoadTeammateRolesCommand);
   ...
}

For most cases, this is perfectly fine, but in a data-heavy model, such as the one being built for my current application, I need to initialize the application and fire off many individual events in order to load my model with base data.

// Load all teammates in the database for display in the reports tab
new TeammateEvent(TeammateEvent.LOAD_TEAMMATES).dispatch();

// Load all effort types
new EffortEvent(EffortEvent.LOAD_TYPES).dispatch();

// Load all projects
new ProjectEvent(ProjectEvent.LOAD_PROJECTS).dispatch();

// Load all roles
new TeammateEvent(TeammateEvent.LOAD_TEAMMATE_ROLES).dispatch();

...

Batch Commands

In order to avoid having to dispatch n events for n commands to be executed, I would rather map 1 event to n commands and have them fired off in sequence. Then, if I need a batch of commands to fire, I only need to dispatch one event, instead of the 4 I used in the example above.

new ApplicationEvent(ApplicationEvent.LOAD_MODEL).dispatch();

Unfortunately, Cairngorm does not allow this, so I had to extend the basic architecture. I created a addCommands() function that maps a single event to multiple commands as an array.

public class Controller extends ExtendedFrontController
{
   public function Controller()
   {
      addCommands(ApplicationEvent.LOAD_MODEL, [LoadTeammatesCommand,
                                LoadEffortTypesCommand,
                                LoadProjectsCommand,
                                LoadTeammateRolesCommand,
                                ...]);
   }
}

Extended Controller Class

Here’s the code that I’ve come up with so far. This is working fine, but there still some things I plan on implementing in the future.

ExtendedFrontController.as

public class ExtendedFrontController extends FrontController
{
   public function ExtendedFrontController()
   {
      super();
   }

   public function addCommands(commandName:String, commandRefs:Array, useWeakReference:Boolean = true):void
   {
      if(commands[ commandName ] != null)
      {
         throw new CairngormError( CairngormMessageCodes.COMMAND_ALREADY_REGISTERED, commandName );
      }

      if (commandRefs == null)
      {
         throw new Error("The commandRefs argument to addCommands() is null");
      }

      commands[commandName] = commandRefs;

      for (var priority:uint = 0; priority < commandRefs.length; priority++)
      {
         CairngormEventDispatcher.getInstance().addEventListener(commandName, executeCommands, false, priority, useWeakReference);
      }
   }

   protected function executeCommands(event:AbstractEvent):void
   {
      var commandsToInitialise:Array = getCommands(event.type);
      var commandRef:Class;

      for (var i:uint = 0; i < commandsToInitialise.length; i++)
      {
         commandRef = commandsToInitialise[i];
         var commandToExecute:ICustomCommand = new commandRef();
         commandToExecute.execute(event);
      }
   }

   protected function getCommands(commandName:String):Array
   {
      var commands:Array = commands[commandName];

      if ( commands == null )
         throw new CairngormError( CairngormMessageCodes.COMMAND_NOT_FOUND, commandName );

      return commands;
   }
}