Spring ActionScript: The Operation API

ActionScript, Air, Flash, Flex, Spring ActionScript 2 Comments »

Introduction

As Flex and Flash developers, we are used to working with asynchronous code. Whenever we load data from a server for instance, we need to wait for the result to come back as we can't just read the result of such a call directly. The way in which we wait for these asynchronous calls to complete differs from API to API. There are events, async tokens, callback functions, ... The fact that there is no unified approach to this, is a core problem in our opinion of Flash. It is not only confusing to developers, but it also prevents us from easily integrating these various techniques and abstracting away their details.

Consider a service that manages User objects by providing basic operations on these entities like create, read, update and delete (CRUD). If you plan on creating an interface for this user service so you can vary the implementation easily, what return type would the methods have?

Actionscript:
  1. public interface IUserService {
  2.   function addUser(user:User): ? ;
  3.   function deleteUser(user:User): ? ;
  4.   function saveUser(user:User): ? ;
  5.   function loadAllUsers(): ? ;
  6. }

We could either:

  • return nothing and wait for events to be dispatched by the service when its methods have executed: this has the disadvantage that there is no real contract on the method.
  • return nothing and pass in a Responder or result and fault handler callback functions: this has the disadvantage that it blurs the signature of the service method with technical details.
  • return an AsyncToken: this is a stricter contract, but it would tie our code to the Flex framework and it would only work if you create implementations that support AsyncToken

In general, the biggest problem is that there is no standard way of defining an asynchronous execution in the Flash Player.

Operation API

The solution Spring ActionScript provides comes in the form of the Operation API. This is a set of interfaces and classes that enables developers to work with asynchronous processes in a uniform fashion. An "operation" in Spring ActionScript vocabulary is the term used for an asynchronous execution and is defined by the IOperation interface. Once an operation is created, it executes immediately and when it is done executing, it either dispatches a "complete" event or an "error" event.

Going back to our user service, this means that we would type all return types of its (asynchronous) methods to IOperation:

Actionscript:
  1. public interface IUserService {
  2.   function addUser(user:User):IOperation;
  3.   function deleteUser(user:User):IOperation;
  4.   function saveUser(user:User):IOperation;
  5.   function loadAllUsers():IOperation;
  6. }

Whether you now have an implementation that uses Remote Objects, Webservices, locally loaded XML data, a local SQLite database, ... clients working with the user service should not know and care about implementation details. All that they know is that asynchronous methods return an operation to which they can attach listeners to for dealing with the result or error of the call.

A client calling one of these methods might to something like the following, considering there is a userService of type IUserService:

Actionscript:
  1. var user:User = new User("John", "Doe");
  2. var operation:IOperation = userService.saveUser(user);
  3. operation.addCompleteListener(saveUser_completeHandler);
  4. operation.addErrorListener(saveUser_errorHandler);
  5.  
  6. function saveUser_completeHandler(event:OperationEvent):void {
  7.   // user was saved
  8.   // event.result contains an optional result of the asynchronous method
  9. }
  10.  
  11. function saveUser_errorHandler(event:OperationEvent):void {
  12.   // user was not saved, since an error occurred
  13.   // event.error contains error information
  14. }

Notice that each operation has the convenience methods "addCompleteListener" and "addErrorListener" for listening to the events dispatched by the operation. The handlers for these events receive an instance of the OperationEvent which contains either the result or the error details.

We have also included some base classes that make it easy to create operations and services. The AbstractOperation class for instance provides a basic implementation of the IOperation interface and implements the event dispatching functionality. You might want to extend this class if you create your own operations.

Here's an example of a custom operation:

Actionscript:
  1. public class MyOperation extends AbstractOperation {
  2.  
  3.   public function MyOperation() {
  4.     // execute something asynchronously
  5.     // the resultHandler and errorHandler deal with the result of fault
  6.   }
  7.  
  8.   private function resultHandler(aResult:Object):void {
  9.     dispatchCompleteEvent(aResult);
  10.   }
  11.  
  12.   private function errorHandler(anError:Object):void {
  13.     dispatchErrorEvent(anError);
  14.   }
  15.  
  16. }

Notice that we call the "dispatchCompleteEvent" and "dispatchErrorEvent" on either success or failure. We pass in the result or the error we received. These will internally be set on the operation so that clients can request them via the OperationEvent.

The Spring ActionScript framework also contains a series of operation implementations for working with Remote Objects, NetConnections, etc.

Combining Operations into Services

Taking this a step further, we also provide base classes for creating services that consist of operations. Going back to our user service, we might create an implementation that uses RemoteObject by subclassing the RemoteObjectService class. This class provides basic functionality for delegating to the underlying Remote Object. Here's an example:

Actionscript:
  1. public class UserService extends RemoteObjectService implements IUserService {
  2.  
  3.   public function UserService(remoteObject:RemoteObject) {
  4.     super(remoteObject);
  5.   }
  6.  
  7.   public function addUser(user:User):IOperation {
  8.     return call("addUser", user);
  9.   }
  10.  
  11.   public function deleteUser(user:User):IOperation {
  12.     return call("deleteUser", user);
  13.   }
  14.  
  15.   public function saveUser(user:User):IOperation {
  16.     return call("saveUser", user);
  17.   }
  18.  
  19.   public function loadAllUsers():IOperation {
  20.     return call("loadAllUsers");
  21.   }
  22.  
  23. }

Notice how easy it is to abstract away the use of a remote object and make clients unaware of this by only providing them with the IUserService interface. The "call" method is a very convenient method to forward the call to the remote object, without having to take care of AsyncTokens and Reponders.

Progress Operations

For operations that take longer to execute the IOperation interface has a subinterface called IProgressOperation. This might for instance be the loading of a sound file, a module, etc. Implementations of this interface can let clients know about the progress the operation made by dispatching progress events at certain intervals. When listening for these events, via the "addProgressListener" method, a ProgressOperationEvent is received that contains a "progress" and "total" property.

Actionscript:
  1. var loadSoundOperation:IProgressOperation = new MyLoadSoundOperation("aSoundFile.mp3");
  2. loadSoundOperation.addProgressListener(loadSoundOperation_progressHandler);
  3.  
  4. function loadSoundOperation_progressHandler(event:ProgressOperationEvent):void {
  5.   trace("Loaded " + event.progress + " of " + event.total + " bytes");
  6. }

Batch Operations

When you are in a scenario where you need to execute several operations and you don't care about the order in which they finish, you might want to use the OperationQueue class. This is an implementation of the IProgressOperation interface that lets you add operations to a queue and receive information about the progress of the complete execution. Since this also is an IOperation, we can listen to the completion of the queue via the "addCompleteListener" method.

Actionscript:
  1. var queue:OperationQueue = new OperationQueue();
  2.  
  3. // setup event listeners
  4. queue.addCompleteListener(queue_completeHandler);
  5. queue.addProgressListener(queue_progressHandler);
  6.  
  7. // add operations to the queue
  8. queue.addOperation(new AnOperation());
  9. queue.addOperation(new AnotherOperation());
  10. queue.addOperation(new YetAnotherOperation());
  11.  
  12. function queue_completeHandler(event:OperationEvent):void {
  13.   trace("Queue is done executing.");
  14. }
  15.  
  16. function queue_progressHandler(event:ProgressOperationEvent):void {
  17.   trace("Executed " + event.progress + " of " + event.total + " operations");
  18. }

Mocking Operations

During development it is often good to have mock or stub implementations of the services that normally connect to a backend. The main reason is that this provides a way of (unit) testing the use of the services without the backend running. Spring ActionScript provides a class called MockOperation for this purpose. Its constructor takes the following arguments:

  • result: the result returned by the operation
  • delay (optional): the time the operations waits before completing, in milliseconds (default = 1000)
  • returnError (optional): a flag indicating wether or not the operation should return an error at random times (default = false)

If you would mock the user service, you could do this as follows (only one method is implemented):

Actionscript:
  1. public function loadAllUsers():IOperation {
  2.   var users:ArrayCollection = new TypedCollection(User);
  3.   users.addItem(new User("John", "Doe"));
  4.   users.addItem(new User("Mitch", "Hedberg"));
  5.   return new MockOperation(users);
  6. }

Conclusion

The Operation API provides a neat way of abstracting away technical details and makes it easier to swap implementations without affecting any code. It unifies asynchronous programming for the Flash Player into a simple set of interfaces and base classes. You will also see that it is the basis for other APIs in the Spring ActionScript framework such as the Command and Task API.

For more information on Spring ActionScript, refer to www.springactionscript.org


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Spring ActionScript 1.0RC1 Released

ActionScript, Air, Flex, Inversion of Control, Spring ActionScript 5 Comments »

Dear community,

I'm pleased to announce that the first release candidate for Spring ActionScript 1.0 is now available.

Download | API Documentation | HTML Docs | Changelog

This release is considered feature complete and announces the final stage before moving to a final 1.0 release.

Amongst the many updates and fixes, these are the most important ones:
- Operation API (docs, blog)
- EventBus (docs)
- Task API (docs)
- MXML Configuration (docs)

These changes will be discussed in a couple of follow-up blogposts, so please check back soon for that. In the meantime, please refer to the documentation provided for more information.

I would like to thank all of you who have given us valuable feedback and provided interesting ideas, sent in patches, reported bugs or helped us out in whatever way possible. Your support has been priceless in the development of this framework and it would not be as rich as it is now without you.

Special thanks go out to Martino for mastering the Maven build system and fixing a lot of the problems that stopped us from releasing earlier, and to Roland who has written 99,9% of the AWESOME documentation and took a natural lead in the development of 1.0RC1. You guys rock!

Enjoy this release and have fun coding!

Spring ActionScript 1.0RC1 Changelog

  • fixed constructor-arg type attribute was not declared in spring-actionscript-objects-1.0.xsd.

    config reference link

  • added unregisterStageProcessor() method to the IStageProcessorRegistry interface.

    ASDoc link

  • added DefaultFlexAutowireProcessor that handles binding using the [Autowired(name="objectName",property="property.chain")] metadata.

    documentation link

  • added PureMVC documentation section.

    puremvc documentation link

  • added autowiring support for injecting external property values using [Autowired(externalProperty="propertyName")].

    documentation link

  • added parent context support to IObjectFactory interface and AbstractObjectFactory implementation.

    documentation link

  • added skip-postprocessor and skip-metadata support for ObjectDefinition.

    skip-metadata link

    skip-postprocessor link

  • added name and value attribute support to the property XML element, for defining external properties directly in the configuration instead of an external properties file.

    docs link

  • added parent properties support to XMLObjectFactory.

    docs link

  • added IStageProcessorRegistry interface and FlexStageProcessorRegistry implementation to support stage wiring in a multi-module situation (Thanks to Arnoud for helping debug this).

    docs link

  • added extra functionality to the CairngormFrontController that enables to add commands that are being created by the application context (basically using the context as a commandFactory).

    cairngorm docs link

  • renamed SASCairngormEvent to SASCairngormModuleEvent.

    cairngorm docs link

  • ObjectUtils no longer has a dependency on the Flex framework.
  • added core.* package containing the Operation API.

    operation docs link

  • added core.event.* package containing the EventBus classes.

    eventbus docs link

  • added EventHandlerMetaDataPostProcessor for automatic handling of EventBus events.

    eventbus docs link

  • added parallel or sequencential execution to CompositeCommand class.

    operation docs link

  • added core.task.* package.

    operation docs link

  • added support for property chain retrieval in the FieldRetrievingObjectFactory, for example: Application.application.systemManager.stage can now be retrieved.
  • added support for compound property names in XML config.
  • added support for factory-object in AbstractObjectFactory..

    link

  • added TaskNamespaceHandler for core.task.* package.

    link

  • added createInstance(clazz:Class, constructorArguments:Array = null) method to the IObjectFactory interface to enable a factory to create objects with only annotations and have no object definition.

    link

  • added operation and service for remoting via NetConnection.

    link

  • added support for vector type in XML configuration..

    link

  • Allow adding mx.flash.Event items to EventSequence and facilitate custom trigger calculation. (Thanks Jurgen).
  • Added modulePolicy of type ModulePolicy to FlexStageProcessorRegistry that determines how modules will be handled.
  • Minor documentation fixes. (Thanks Mark).
  • added RouteEventsMetaDataPostProcessor to re-route events from arbitrary objects through the EventBus.

    link

  • Added IApplicationDomainAware and ApplicationDomainAwarePostProcessor to automatically inject the context's application domain in objects that need a reference.
  • Added MethodInvokingFactoryObject to be able to inject method results from other objects.

    link

  • Added util:invoke element as a configuration shortcut for the MethodInvokingFactoryObject.

    link

  • Added support for injecting the application context by using ref="this" in the configuration.

    link

  • Added Maven mojo to generate a flex-config file from an application context (Thanks Ryan).

    link


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

PNGSizeExtractor

ActionScript, Air, Flash, Flex 3 Comments »

I was trying to read the dimensions of some very large image files today when I bumped into the Flash Player 10 limitations regarding maximum image sizes. (More info about this is available here.) Since an image file that is too big will not be shown correctly, there is no use in uploading and using it. I wanted to provide detailed feedback about the image dimensions and how they exceeded the allowed sizes, but that was not possible unfortunately since the width and height properties of the loaded image would remain 0. (I posted a question on StackOverflow about this issue here.)

The JPGSizeExtractor class by Antti Kupila enables you to read the width and height of a JPG by reading the image's bytecode. Since we also need to support PNG files, I thought I'd create a simple PNGSizeExtractor myself.

Here's the code:

Actionscript:
  1. package com.herrodius.utils {
  2.    
  3.     import flash.utils.ByteArray;
  4.  
  5.     /**
  6.      * Reads the width and height from a PNG image.
  7.      *
  8.      * http://en.wikipedia.org/wiki/Portable_Network_Graphics
  9.      * http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html
  10.      *
  11.      * @author Christophe Herreman
  12.      */
  13.     public class PNGSizeExtractor {
  14.  
  15.         private static const SIGNATURE_BYTES:Array = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
  16.  
  17.         private static const CHUNK_TYPE_SIZE:uint = 4;
  18.  
  19.         // --------------------------------------------------------------------
  20.         //
  21.         // Constructor
  22.         //
  23.         // --------------------------------------------------------------------
  24.  
  25.         public function PNGSizeExtractor(byteArray:ByteArray) {
  26.             decode(byteArray);
  27.         }
  28.  
  29.         // --------------------------------------------------------------------
  30.         //
  31.         // Public Properties
  32.         //
  33.         // --------------------------------------------------------------------
  34.  
  35.         // ----------------------------
  36.         // width
  37.         // ----------------------------
  38.  
  39.         private var _width:uint;
  40.  
  41.         public function get width():uint {
  42.             return _width;
  43.         }
  44.  
  45.         // ----------------------------
  46.         // height
  47.         // ----------------------------
  48.  
  49.         private var _height:uint;
  50.  
  51.         public function get height():uint {
  52.             return _height;
  53.         }
  54.  
  55.         // --------------------------------------------------------------------
  56.         //
  57.         // Private Methods
  58.         //
  59.         // --------------------------------------------------------------------
  60.  
  61.         private function decode(data:ByteArray):void {
  62.             readSignature(data);
  63.             readIHDR(data);
  64.         }
  65.  
  66.         private function readSignature(data:ByteArray):void {
  67.             for (var i:uint = 0; i<SIGNATURE_BYTES.length; i++) {
  68.                 if (data.readUnsignedByte() != SIGNATURE_BYTES[i]) {
  69.                     throw new Error("File is not a PNG file.");
  70.                 }
  71.             }
  72.         }
  73.  
  74.         private function readIHDR(data:ByteArray):void {
  75.             var size:uint = data.readUnsignedInt();
  76.             var type:String = data.readUTFBytes(CHUNK_TYPE_SIZE);
  77.             _width = data.readUnsignedInt();
  78.             _height = data.readUnsignedInt();
  79.         }
  80.        
  81.     }
  82. }

And here's how to use it. (byteArray is the data of the loaded PNG):

Actionscript:
  1. var pngDecoder:PNGSizeExtractor = new PNGSizeExtractor(byteArray);
  2. trace(pngDecoder.width, pngDecoder.height);

And here is a small sample application that loads the file locally:

Actionscript:
  1. <?xml version="1.0"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" horizontalAlign="left">
  3.  
  4.     <mx:Script>
  5.         <![CDATA[
  6.         import com.herrodius.utils.PNGSizeExtractor;
  7.  
  8.         import mx.controls.Alert;
  9.  
  10.         private var _fileReference:FileReference;
  11.  
  12.         private function browseButton_clickHandler(event:MouseEvent):void {
  13.             _fileReference = new FileReference();
  14.             _fileReference.addEventListener(Event.SELECT, fileReference_selectHandler);
  15.             _fileReference.addEventListener(Event.COMPLETE, fileReference_completeHandler);
  16.             _fileReference.browse();
  17.         }
  18.  
  19.         private function fileReference_selectHandler(event:Event):void {
  20.             _fileReference.load();
  21.         }
  22.  
  23.         private function fileReference_completeHandler(event:Event):void {
  24.             fileNameTextInput.text = _fileReference.name;
  25.  
  26.             try {
  27.                 var pngDecoder:PNGSizeExtractor = new PNGSizeExtractor(_fileReference.data);
  28.                 textArea.text = "Width: " + pngDecoder.width + "\n";
  29.                 textArea.text += "Height: " + pngDecoder.height;
  30.             } catch (e:Error) {
  31.                 Alert.show(e.message, "Error decoding image");
  32.             }
  33.         }
  34.  
  35.         ]]>
  36.     </mx:Script>
  37.  
  38.     <mx:Label text="Browse for a PNG file." fontWeight="bold"/>
  39.  
  40.     <mx:HBox width="100%">
  41.         <mx:TextInput id="fileNameTextInput" width="100%" editable="false"/>
  42.         <mx:Button id="browseButton" label="..." click="browseButton_clickHandler(event)"/>
  43.     </mx:HBox>
  44.  
  45.     <mx:TextArea id="textArea" width="100%" height="100%"/>
  46.  
  47. </mx:Application>


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 
WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in