Wednesday, June 30, 2010

ADF Coding Standards

from ADF Enterprise Methodology Group

Technologies in Scope

These standards cover applications built using ADF Faces and ADF Business Components and are intended for ADF 11g onwards - some may be suitable for earlier versions but that is down to the reader’s interpretation. The standards are designed to apply to moderate-sized enterprise applications (say 100 UI pages for example) but hopefully with some subdivision of packages would be suitable for far larger applications too. Smaller applications (say 10-20 pages) may find this overkill, although it is always worth bearing in mind that a successful small project can often evolve into a much larger one! The focus at this stage is for UI-driven applications, rather than ones providing predominantly web service interfaces, though this may change over time.

Why Have Coding Standards?

Standards help promote a uniform ‘look and feel’ to code, it makes the code easier to maintain as it becomes more intuitive where to find things. By giving developers guidance as to how to name items, coding standards save time and effort. Finally by setting out the way code is organised standards can improve application structure and foster best practices.
Currently this document is restricted in scope to cover the naming conventions for ADF items and packages (and by implication logical structure); other more detailed, say, Java level coding standard are covered elsewhere.
The intention is that the standards should fit comfortably with the way JDeveloper works and existing applications have been built – hopefully many of them are in common use already (for example, as a result of developers learning ADF by studying SRDemo).

References

[SR] SRDemo Application – sample application produced by Oracle (10g onwards)
[TU] The Ultimate Human Resources Application (TUHRA) – sample application developed in book by Peter Koletzke and Duncan Mills (10.1.3 based)
[OA] Oracle Applications Framework Developer’s Guide (Metalink 394780.1, 10.1.3 based)
[JHS] Java Headstart – an ADF code generation tool from Oracle Consulting (10.1.3 based)
[FOD] Fusion Order Demo – sample application produced by Oracle (11g onwards)

Structure of these Standards

These standards are grouped by topic area for ease of reference: General (G), Entity Objects (EO), View Objects (VO), AMs (AM), Backing Beans (BB), Task Flows (TF). As a general rule terms are described in full except where an abbreviation is very well known (e.g. FK).

Definitions

This document uses the following definitions:
  • System – the super entity of sub-systems
  • Sub-system – a single application such as HR, procurement
  • Application – synonymous with a sub-system, not to be confused with an ADF Application Workspace
  • Common ADF Application Workspace – a single ADF Application Workspace encapsulating the common parts of all other Application Workspaces, such as EOs, lookup VOs, but not Task Flows.
  • Task Flow ADF Application Workspace – a single ADF Application Workspace encapsulating the components for a single Task Flow, comprised of both Model & ViewController projects,
  • Master ADF Application Workspace – a composite ADF Application Workspace reusing Task Flow ADF Application Workspaces to make a single Application (see above)
Note in the following descriptions any annotation (DEFAULT) is the current JDev behaviour.

General

G1) Application name - every app must have a short name or acronym that can be used as part of package names, specific classes and on paths. This should be used in lower case for speed and simplicity (and to save wearing out the shift key). E.g. srdemo, fodemo, um, foobar, speaker
G2) Packages should be prefixed by the client or company name, though arguably[1] not a fully qualified path, e.g. oracle.srdemo, veriton.um, ukoug.speaker.
G3) Framework extensions should be in a separate package[2], called "Framework" perhaps (called FrameworkExtensions in [SR] and frmwkext in [FOD]).
G4) Put the BC in a project called Model (note: some places have used DataModel etc but we suggest using Model to be make it clear it’s that part of the MVC architecture).
G5) Top level BC directories – ‘entities’, ‘design’ for diagrams, ‘service’ for application modules. Note: the ‘views’ packages typically organised within the individual task flows that use them (see later).
G6) Organise your directories to suit project lifecycle and source control system (typically Subversion these days) and library management tools like Maven. E.g. your build directories have to be outside your trunk folder. E.g.src\main\java, src\test\java folder for unit testing. Include a ‘database’folder for your SQL schema creation scripts etc.

Templates

Templates are another significant 11g feature. Your application should have one or more templates, though these may be imported from an organisation-wide library.

Application Structure and Task Flows

ADF/JDeveloper 11g introduces new functionality called Task Flows. These dramatically improve the way you use ADF to build UIs; modern ADF applications are expected to use them extensively.

Your overall system should be comprised of several Task Flow ADF Application Workspaces, each representing a Task Flow modelling a business process, as well as a single Common ADF Application Workspace.

A Master ADF Application Workspace will include each of the separate Task Flow ADF Application Workspaces, in order to meet the overall Application's needs.



Therefore with a new UI project one of the first considerations will be what task flows are required.

Task Flows

A task flow is contains a set of UI components which typically references the model (ADF BC components) held elsewhere in the application. Within a task flow there will be the usual ViewController items which should following these guidelines:
TF1) - the ViewController package naming convention as its first part will reflect the Task Flow name: eg. createemployees
TF2) .view - the code specific to view layer within the ViewController package will include the sub-package name "view" (DEFAULT)
TF3) .view.backing - ViewController backing beans should go into a sub-package name "backing". (DEFAULT)
TF4) .view.framework – used for overriden component classes.
TF5) .view.pageDefs – for the ADFm binding page def files (DEFAULT)
TF6) .view.servlet – any servlet, filter or listener code that extends the JEE servlet that is used in generating output. eg. a servlet that dynamically renders a BLOB into a downloadable file.
TF7) .controller - code specific to the controller layer within the ViewController package will include the sub-package name ‘controller’
TF8) .controller.lifecycle – code that overrides the ADF controller such as the ErrorHandlerImpl class.
TF9) .controller.managed – ViewController managed beans should go into a sub-package name "managed"

TF10) .controller.servlet - any servlet, filter or listener code that extends the JEE servlet that is used in handling input. eg. a servlet that manages security based on a non-standard login mechanism.

View Controller

VC1) Put View Controller code in a ‘ViewController’ project.
VC2) For each ADFm page def file, its name should reflect the web page it supports with the suffix PageDef.Eg. ViewEmployeesPageDef. (DEFAULT)
VC3) The name of each managed bean should reflect their function, eg. Versioner or CartProcessor, and not include information about its scope etc (since this can change).

Backing Beans

BB1) All backing beans should be in a ‘backing’ package
BB2) Backing beans should have the same name as the web page that they are used in, eg. ViewEmployees.jspx has a backing bean createemployees.view.backing.ViewEmployees
BB3) Backing beans should implement java.io.Serializable if the application is to be deployed on cluster

BC Projects

BC1) If you anticipate having more than around 50 entities you should we have separate model projects for different data subsystems or application functionality areas. This allows developers to concentrate on the model project they are currently working on. When it’s needed to reuse ADF BC
components between different model projects, use the import functionality for ADF BC with JAR files.

BC Entity Objects

EO1) Entities - put associations in an ‘associations’ sub-package to keep them out of the way of the other entities (which can get quite a large list anyway)
EO2) For clarity entity names should always same as the underlying table. Aino suggests "Reference entities (and their attributes) are prefixed with ‘Lkp’ for lookup, i.e. ‘Lkp’. I would suggest for a moderately sized project these should be in a separate ‘lookups’ sub-package.
EO3) Transient attributes, calculated on the EO, should be prefixed with 'Trnsnt"

BC View Objects

VO1) Put View Links in a "links" sub-package ("links" is in [FOD], "viewlinks" is in [SR]?)
VO2) Names of Views - some sort of rule for how to name, e.g. ReadingsByMeterVo[1] (note the java convention of making only the first letter upper case, even when an abbreviation like VO). I've used "Vo" suffix, FODemo has "VO" suffix. Aino suggests "Vw" suffix. If it is a read-only VO then rather than use a prefix or suffix put it into a sub-package (e.g. app.model.view.ro) to save, sometimes very difficult, refactoring in case it needs to be changed to a R/W VO later. The same should be applied to LOV views – we recommend a separate package such as app.model.view.lov. The Views themselves should be named according to the functionality/data that it provides (this may be quite far removed from the underlying tables for more complex joins).
VO3) ViewLink names - if you have aliases for tables and FKs based on those the defaults work pretty well I reckon (I always have 3 char aliases and so an FK might be ABC_DEF_FK in which case the view link becomes "AbcDefFkLink"). Note that view link names are visible through the AM so descriptive may be better. However there has been debate on this – some people prefer to invent more descriptive names; it probably depends on how database-centric or java-centric your development team is (and probably how sensibly your foreign keys have been named in the database too!).
VO4) ViewObject instance names in AM - interesting one - do you rename or do you leave the JDev default ? Currently I rename but I'm not sure it's worth the effort.
VO5) Bundle up View Objects and View Links into functional subsystems and package with task flows if you are using them.

BC Application Modules

AM1) Application Module naming - seems to be nicer to call it Service these days. How about multiple application modules? How about nested vs root names?
AM2) If your project is fairly big then you should consider bundling up entities, and possibly views, into separate application modules which can then be nested in your final applications.

An Example Project Structure

As a means of illustrating the application structure here is an example project:


  • Common (app.common)
  • UnitTest (app.unittest)
  • Framework (app.framework)
  • Model (app.model)
  • app.model.services
  • app.model.entities
  • app.model.entities.associations
  • TaskFlows (app.tasks)
  • app.tasks.task1
  • app.tasks.task1.views
  • app.tasks.task1.views.viewlinks
  • app.tasks.task1.backing
  • app.tasks.task2
  • app.tasks.task2.views
  • app.tasks.task2.views.viewlinks
  • app.tasks.task1.backing
  • app.tasks.task3
  • app.tasks.task3.views
  • app.tasks.task3.views.viewlinks
  • app.tasks.task1.backing
  • ViewController (non-task flow specific UI)
  • app.ui.backing
  • app.ui.utility
  • app.ui.pageDefs

Note: app is the application name, but could also be of the form com.company.app etc.

Final Comments

There's plenty more to think about (java, resources, internationalisation) but hopefully this is a start anyway. At the moment we don’t know of any other publically available ADF coding standards document except for OA (which as 10g based so doesn’t cover task flows) and there still needs to be an activity of merging in best practices from that. In addition JHeadstart ought to enshrine many ADF best practices so should also be considered by this document. Any volunteers would be most welcome! The intention is that this is a living document that will evolve with new ADF/JDeveloper releases and become a resource helpful to the whole community.

Footnotes

[1] This point is slightly controversial and has changed over the years. It has been fashionable to include a reverse domain name, e.g. uk.co.veriton.um, but this has the disadvantage of making quite a few nested directories without a clear benefit, i.e. you are unlikely to have other projects in the ‘com.’ domain as compared to the ‘com.company’ domain (even more so for countries, like the UK, that have commercial organisations in a sub-domain, e.g. uk.co.company).
[2] Further discussion required (separate project? - I would really like a more universally applicable one, though as it becomes more generic potentially its value decreases - see the ADFutils debate)
[1] John Flack thinks we should use VO (and EO) to save confusion with database views etc

Sunday, May 16, 2010

home project: wall color blocking

  1. Design the color blocks
    After searching for a while for a color blocking online tool for random generating tiles I gave up. There are a few tile generators and color maping from a palette tools but weren't helpful for what I had in mind.

    So I choose to use the cacoo online diagram tool to make the blocks by hand. I measured the wall first(410x240cm) and started piling the blocks. As base size I used a 20x20cm block and used the scale of 1cm-1px in the diagram. You can view the resulted design here which has a size of 380x200cm

  2. Split the wall in tiles
    This step is the most important and demanding of the whole process. Because I didn't have a perfect rectangle wall and I couldn't make sure the floor and ceiling were parallel and perpendicular on the adjacent walls I choose to start from left bottom corner and make my way up, right.
    Took me almost half of day to measure, and mark the small crosses defining the 20x20 tiles from the 380x200 surface.Is true I got a bit paranoid on making it perfect :), but the end result was satisfying.
  3. Put sticky paper around the blocks
    This was easy. All I had to take care of, were the parts where blocks overlapped. Just to be safe buy some extra sticky tape, I had to visit the store twice :)...
  4. Mix colors and ...start painting
    I will let the pictures explain it..








  5. Cleaning
    What a mess...I don't feel so eager to finish this step and I wonder why...

Tuesday, February 9, 2010

rome, italy

Sunday, December 13, 2009

Flex upload and download with Blazeds

from  Edwin Biemond

With Actionscript 3 and Flashplayer 10 you can now use the new FileReference features. For example you download and upload a file with RemoteObjects ( Blazeds / LifeCycle ). In the early days you had to use a upload and download servlets.
To use the new FileReference features please read these two articles flexexamples.com and using flashplayer 10

Here are some screenshots of the flex application.
First we can upload a file. The datagrid show the status of the uploaded files.

Off course we can download some files from a remote server. First we need to get a list of the remote files. For this we need to press the Get remote files button.

We can select a file and press the Retrieve File Button. When the status is ready we can save this file by pressing the Save File Button.

Here you can download the Flex source code

The code of the upload panel
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" 
 layout="vertical" width="100%"  height="100%"
 title="Upload Files">


 <mx:Script>
  <![CDATA[
   import mx.rpc.AsyncToken;
   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;
    
   private var refUploadFile:FileReference;

   private var UploadFiles:Array = new Array();
   
   
   // Called to add file(s) for upload
   private function addFiles():void {
    refUploadFile = new  FileReference();
      refUploadFile.browse();
      refUploadFile.addEventListener(Event.SELECT,onFileSelect);
      refUploadFile.addEventListener(Event.COMPLETE,onFileComplete);
   }

   // Called when a file is selected
   private function onFileSelect(event:Event):void {
    UploadFiles.push({  name:refUploadFile.name,
         size:formatFileSize(refUploadFile.size),
         status:"initial"});
    listFiles.dataProvider  = UploadFiles;
    listFiles.selectedIndex = UploadFiles.length - 1;
     
                refUploadFile.load();
                for ( var i:int = 0 ; i <  UploadFiles.length ; i++ ) {
                  if( UploadFiles[i].name == refUploadFile ) {
                   UploadFiles[i].status = "loaded";
        listFiles.dataProvider  = UploadFiles;
                   break;
                  }
                }
   }

   // Called to format number to file size
   private function formatFileSize(numSize:Number):String {
    var strReturn:String;
    numSize = Number(numSize / 1000);
    strReturn = String(numSize.toFixed(1) + " KB");
    if (numSize > 1000) {
     numSize = numSize / 1000;
     strReturn = String(numSize.toFixed(1) + " MB");
     if (numSize > 1000) {
      numSize = numSize / 1000;
      strReturn = String(numSize.toFixed(1) + " GB");
     }
    }    
    return strReturn;
   }

   
 
     private function onFileComplete(event:Event):void
     {
                refUploadFile = event.currentTarget as FileReference;
                var data:ByteArray = refUploadFile.data;  
                var loader:Loader = new Loader();  
                loader.loadBytes(data);  
                
       var token:AsyncToken = AsyncToken(
                                 remoteUpload.doUpload(data, refUploadFile.name)
                                 );
       
             token.kind = refUploadFile.name;
                          
                for ( var i:int = 0 ; i <  UploadFiles.length ; i++ ) {
                  if( UploadFiles[i].name == refUploadFile ) {
                   UploadFiles[i].status = "upload";
        listFiles.dataProvider  = UploadFiles;
                   break;
                  }
                }
     }

     private function uploadResultHandler(event:ResultEvent):void
     {
                for ( var i:int = 0 ; i <  UploadFiles.length ; i++ ) {
                  if( UploadFiles[i].name == event.token.kind ) {
                   UploadFiles[i].status = "finished";
        listFiles.dataProvider  = UploadFiles;
                   break;
                  }
                }
     }

     private function faultResultHandler(event:FaultEvent):void
     {
                for ( var i:int = 0 ; i <  UploadFiles.length ; i++ ) {
                  if( UploadFiles[i].name == event.token.kind ) {
                   UploadFiles[i].status = "error";
        listFiles.dataProvider  = UploadFiles;
                   break;
                  }
                }
     }

   
  ]]>
 </mx:Script>

 <mx:RemoteObject id="remoteUpload" destination="FileUtils" 
  result="uploadResultHandler(event)"
  fault="faultResultHandler(event)"/>
 
 
 <mx:Canvas width="100%" height="100%">
  <mx:DataGrid id="listFiles" left="0" top="0" bottom="0" right="0"
   allowMultipleSelection="true" verticalScrollPolicy="on" 
   draggableColumns="false" resizableColumns="false" sortableColumns="false">
   <mx:columns>
    <mx:DataGridColumn headerText="File" width="150" dataField="name" wordWrap="true"/>
    <mx:DataGridColumn headerText="Size" width="50" dataField="size" textAlign="right"/>
    <mx:DataGridColumn headerText="Status" width="50" dataField="status" textAlign="right"/>
   </mx:columns>
  </mx:DataGrid>
 </mx:Canvas>
 <mx:ControlBar horizontalAlign="center" verticalAlign="middle">
  <mx:Button id="btnAdd" toolTip="Add file(s)" click="addFiles()" 
   label="Upload Files" width="150"/>
 </mx:ControlBar>
</mx:Panel>


The code of the download panel
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" 
 width="100%" height="100%" title="Download Files">

 <mx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;
   import mx.rpc.AsyncToken;

   private var UploadFiles:Array = new Array();
   private var UploadFilesColl:ArrayCollection = new ArrayCollection();
   private var fileData:ByteArray = new ByteArray();
   private var fileName:String;

   
     private function uploadResultHandler(event:ResultEvent):void
     {
       if ( event.token.kind == "remoteFileList") { 
               UploadFilesColl = event.result as ArrayCollection;
               for ( var i:int = 0 ; i <  UploadFilesColl.length ; i++ ) {
                 UploadFiles.push({  name:UploadFilesColl[i]
                                  ,  status:"initial"});
               }
               listFiles.dataProvider = UploadFiles;
              } else  {
                 fileData = event.result as ByteArray;
                 fileName = event.token.kind;
                 for ( var b:int = 0 ; b <  UploadFiles.length ; b++ ) {
                  if( UploadFiles[b].name == event.token.kind ) {
                   UploadFiles[b].status = "Ready";
        listFiles.dataProvider  = UploadFiles;
                   break;
                  }
                }

              }
     }

     private function faultResultHandler(event:FaultEvent):void
     {
     }

     private function saveFile(event:Event):void
     {
       var fileReference:FileReference = new FileReference();
       fileReference.save(fileData,fileName);
     }

     private function getRemoteFiles(event:Event):void
     {
       var token:AsyncToken = AsyncToken(remoteDownload.getDownloadList());
             token.kind = "remoteFileList";
     }

     private function getDownload(event:Event):void
     {
       var token:AsyncToken = AsyncToken(
           remoteDownload.doDownload(listFiles.selectedItem.name));
             token.kind = listFiles.selectedItem.name;
     }
  ]]>
 </mx:Script>


 <mx:RemoteObject id="remoteDownload" destination="FileUtils" 
  result="uploadResultHandler(event)"
  fault="faultResultHandler(event)"/>


 <mx:Canvas width="100%" height="100%">
  <mx:DataGrid id="listFiles" left="0" top="0" bottom="0" right="0"
   verticalScrollPolicy="on"
   draggableColumns="false" resizableColumns="false" sortableColumns="false">
   <mx:columns>
    <mx:DataGridColumn headerText="File" width="150" dataField="name" wordWrap="true"/>
    <mx:DataGridColumn headerText="Status" width="50" dataField="status" textAlign="right"/>
   </mx:columns>

  </mx:DataGrid>
 </mx:Canvas>
 <mx:ControlBar horizontalAlign="center" verticalAlign="middle">
  <mx:Button id="btnList" toolTip="List remote files" 
   width="150" 
   label="Get Remote Files"
   click="getRemoteFiles(event)"/>
  <mx:Button id="btnRetrieve" toolTip="Retrieve file" 
    width="150" click="getDownload(event)" label="Retrieve File"/>
  <mx:Button id="btnSave" toolTip="Save file" 
    width="150" click="saveFile(event)" label="Save File"/>
 </mx:ControlBar>

 
 
</mx:Panel>

The java code
package nl.ordina.flex;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import java.util.ArrayList;
import java.util.List;

public class FileUtils {


    public String  doUpload(byte[] bytes, String fileName) throws Exception
    {
        fileName = System.getProperty("java.io.tmpdir") + "/" + fileName;
        File f = new File(fileName);
        FileOutputStream fos = new FileOutputStream(f);
        fos.write(bytes);
        fos.close();
        return "success";
    }

    public List  getDownloadList()
    {
        File dir = new File(System.getProperty('java.io.tmpdir'));
        String[] children = dir.list();
        List dirList = new ArrayList();
        if (children == null) {
               // Either dir does not exist or is not a directory
           } else {
               for (int i=0; i<children.length; i++) {
                   // Get filename of file or directory
                   dirList.add( children[i]);
               }
           }
        return dirList;
    }

    public byte[] doDownload(String fileName)
    {
        FileInputStream fis;
        byte[] data =null;
        FileChannel fc; 

        try {
            fis = new FileInputStream(System.getProperty("java.io.tmpdir") + "/" + fileName);
            fc = fis.getChannel();
            data = new byte[(int)(fc.size())];
            ByteBuffer bb = ByteBuffer.wrap(data);
            fc.read(bb);
        } catch (FileNotFoundException e) {
            // TODO
        } catch (IOException e) {
            // TODO
        }
        return data;
    }
}
"

Friday, October 30, 2009

Friday, October 9, 2009

The Spring ActionScript Framework — Part 3: Injecting Services (and Mock Services)

from Cristophe Coenraets

In part 1 of this series, we saw how Flex AS allows you to externalize the configuration and the wiring of objects. In part 2, we discussed how you can “autowire” view properties. We intentionally kept the example simplistic by directly injecting the contact RemoteObject into the views. In real life, however, you generally don’t want to tie your views to a specific service implementation… The views should be independent from your data access strategy: RemoteObject, HTTPService, WebService, mock service, etc.

To satisfy this requirement, we will create an IContactService interface, and two classes that implement that interface:

  1. ContactRemoteObjectService uses RemoteObject to access contact data
  2. ContactMockService encapsulates mock data as a way of testing the application without backend data

For the purpose of this sample application, IContactService is defined as follows:

package insync.services
{
import insync.model.Contact;
import mx.rpc.AsyncToken;

public interface IContactService
{
 function getContactsByName(name:String):AsyncToken;

 function save(contact:Contact):AsyncToken;

 function remove(contact:Contact):AsyncToken;
}
}

ContactRemoteObjectService is implemented as follows:

package insync.services
{
import insync.model.Contact;

import mx.controls.Alert;
import mx.rpc.AsyncResponder;
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.mxml.RemoteObject;

public class ContactRemoteObjectService implements IContactService
{
 private var remoteObject:RemoteObject;

 public function ContactRemoteObjectService(remoteObject:RemoteObject)
 {
      this.remoteObject = remoteObject;
 }

 public function save(contact:Contact):AsyncToken
 {
  var token:AsyncToken = remoteObject.save(contact);
  token.contact = contact;
         token.addResponder(new AsyncResponder(save_result, faultHandler));
         return token;
 }

 public function remove(contact:Contact):AsyncToken
 {
  var token:AsyncToken = remoteObject.remove(contact);
         token.addResponder(new AsyncResponder(remove_result, faultHandler));
         return token;
 }

 public function getContactsByName(name:String):AsyncToken
 {
  var token:AsyncToken = remoteObject.getContactsByName(name);
         token.addResponder(new AsyncResponder(getContactsByName_result, faultHandler));
         return token;
 }

 /* Result Handlers ---------------------------------------------------------------*/

 private function save_result(event:ResultEvent, token:AsyncToken=null):void
 {
  // For a create operation, assign the generated primary key to the id property
  // of the contact object
  event.token.contact.id = event.result.id;

  // Dispatch an event on the async token. This allows the caller of the method to register as
  // a listener for the result event of the specific method call. (See ContactForm.mxml for an example.
  event.token.dispatchEvent(event);
 }  

 private function remove_result(event:ResultEvent, token:AsyncToken=null):void
 {
  event.token.dispatchEvent(event);
 }  

 private function getContactsByName_result(event:ResultEvent, token:AsyncToken=null):void
 {
  event.token.dispatchEvent(event);
     }  

 private function faultHandler(event:FaultEvent, token:AsyncToken=null):void
     {
      Alert.show(event.fault.faultString + "\n" + event.fault.faultDetail, "Error Invoking RemoteObject");
     }

}
}

NOTE: There is nothing specific to the Spring AS framework in this class, except that the remoteObject property will be injected by the framework. This simple implementation is tied to the Flex framework and specifically the rpc API: the contract between the service and other components of the application relies on mx.rpc classes (AsyncToken and ResultEvent). You can replace this basic implementation with your own. Spring AS also provides an abstraction API for working with asynchronous method calls that is not tied to the Flex framework (see the AbstractRemoteObjectService class). Spring AS aims to be an AS framework and not specifically a Flex framework.

ContactMockService is implemented as follows:

package insync.services
{
import insync.model.Contact;
import mx.collections.ArrayCollection;
import mx.rpc.AsyncResponder;
import mx.rpc.AsyncToken;
import mx.rpc.events.ResultEvent;

public class ContactMockService extends MockService implements IContactService
{
 private var contacts:ArrayCollection = new ArrayCollection();

 private var nextId:int;

 public function ContactMockService()
 {
  var contact:Contact = new Contact();
  contact.id = 1;
  contact.firstName = "Christophe";
  contact.lastName = "Coenraets";
  contacts.addItem(contact);

  contact = new Contact();
  contact.id = 2;
  contact.firstName = "Lisa";
  contact.lastName = "Taylor";
  contacts.addItem(contact);

  contact = new Contact();
  contact.id = 3;
  contact.firstName = "John";
  contact.lastName = "Smith";
  contacts.addItem(contact);

  nextId = 4;
 }

 public function save(contact:Contact):AsyncToken
 {
  if (contact.id == 0) // New contact
  {
   contact.id = nextId++;
   contacts.addItem(contact);
  }
  var token:AsyncToken = createToken(contact);
         token.addResponder(new AsyncResponder(resultHandler, null));
         return token;
 }

 public function remove(contact:Contact):AsyncToken
 {
  var result:ArrayCollection = new ArrayCollection();
  for (var i:int=0; i<contacts.length; i++)
  {
   var current:Contact = contacts.getItemAt(i) as Contact;
   if (current.id == contact.id)
   {
    contacts.removeItemAt(i);
    break;
   }
  }
  var token:AsyncToken = createToken(contacts);
         token.addResponder(new AsyncResponder(resultHandler, null));
         return token;
 }

 public function getContactsByName(name:String):AsyncToken
 {
  var result:ArrayCollection = new ArrayCollection();
  for (var i:int=0; i<contacts.length; i++)
  {
   var contact:Contact = contacts.getItemAt(i) as Contact;
   if (contact.fullName.indexOf(name)>=0)
   {
    result.addItem(contact);
   }
  }

  var token:AsyncToken = createToken(result);
         token.addResponder(new AsyncResponder(resultHandler, null));
         return token;
 }

 private function resultHandler(event:ResultEvent, token:AsyncToken = null):void
 {
  event.token.dispatchEvent(event);
 }

}
}

To keep the views independent from a specific implementation of the service, we provide them with a property of the interface type (IContactService). Depending on the Spring AS application context, a specific implementation of the service is injected. For example, ContactForm is defined as follows:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:controls="insync.controls.*"

 width="100%" height="100%"
 label="{contact.id>0?contact.fullName:'New Contact'}">

 <mx:Script>
 <![CDATA[

 import mx.rpc.events.ResultEvent;
 import insync.events.ContactEvent;
 import insync.services.IContactService;
 import insync.model.Contact;

 [Autowired]
 public var contactService:IContactService;

 [Bindable]
 public var contact:Contact;

 private function save():void
 {
  contact.firstName = firstName.text;
  contact.lastName = lastName.text;
  contact.email = email.text;
  contact.phone = phone.text;
  contact.address = address.text;
  contact.city = city.text;
  contact.state = state.text;
  contact.zip = zip.text;
  contact.pic = picture.source;

  contactService.save(contact).addEventListener(ResultEvent.RESULT,
   function(event:ResultEvent):void
   {
    // Display a status message. Added here to provide an example where
    // a specific contact view needs to be notified of the success or failure
    // of a service operation.
    status.text = "Contact saved successfully";
    setTimeout(function():void{status.text=""}, 1500);
   });
 }

 private function remove():void
 {
  contactService.remove(contact);
 }

 ]]>

 </mx:Script>

 <mx:Form>
  <mx:FormItem label="Id">
   <mx:TextInput text="{contact.id}" enabled="false"/>

  </mx:FormItem>
  <mx:FormItem label="First Name">
   <mx:TextInput id="firstName" text="{contact.firstName}"/>
  </mx:FormItem>

  <mx:FormItem label="Last Name">
   <mx:TextInput id="lastName" text="{contact.lastName}"/>
  </mx:FormItem>
  <mx:FormItem label="Email">

   <mx:TextInput id="email" text="{contact.email}"/>
  </mx:FormItem>
  <mx:FormItem label="Phone">
   <mx:TextInput id="phone" text="{contact.phone}"/>

  </mx:FormItem>
  <mx:FormItem label="Address">
   <mx:TextInput id="address" text="{contact.address}"/>
  </mx:FormItem>

  <mx:FormItem label="City">
   <mx:TextInput id="city" text="{contact.city}"/>
  </mx:FormItem>
  <mx:FormItem label="State">

   <mx:TextInput id="state" text="{contact.state}"/>
  </mx:FormItem>
  <mx:FormItem label="Zip">
   <mx:TextInput id="zip" text="{contact.zip}"/>

  </mx:FormItem>
 </mx:Form>

 <controls:PictureInput id="picture" top="14" left="350" styleName="pictureFrame"

  pictureWidth="160" pictureHeight="160"
  source="{contact.pic}" />

 <mx:Label id="status" left="8" bottom="50"/>

 <mx:HBox left="8" bottom="8">
  <mx:Button label="Save" click="save()"/>
  <mx:Button label="Delete" click="remove()"/>

 </mx:HBox>

</mx:Canvas>

Although very simple, the InSync application allows you to open multiple contact views. It’s therefore important for a specific view to be able to know that a specific operation succeeded or failed. For example, in this case, we show a status message when the save operation succeeded.

If you want to work with the Mock Service implementation, you’d define applicationContext.xml as follows:

<?xml version="1.0" encoding="utf-8"?>

<objects>

 <object id="contactService" class="insync.services.ContactMockService" 

</objects>

If you want to switch to the RemoteObject implementation, you’d change the applicationContext.xml as follows. No need to change anything in the application, and no need to recompile.

<?xml version="1.0" encoding="utf-8"?>

<objects>

 <object id="remoteObject" class="mx.rpc.remoting.mxml.RemoteObject" abstract="true">
  <property name="endpoint" value="http://localhost:8400/lcds-samples/messagebroker/amf" />

  <property name="showBusyCursor" value="true" />
 </object>

 <object id="contactRemoteObject" parent="remoteObject">

  <property name="destination" value="contacts" />
 </object>

 <object id="contactService" class="insync.services.ContactRemoteObjectService">

     <constructor-arg>
         <object id="myContactRemoteObject" parent="remoteObject">
             <property name="destination" value="contacts" />

         </object>
     </constructor-arg>
 </object>

</objects>

In this version of the application, we achieved an important goal: we decoupled the views of the application from any specific service implementation (and we can easily change the implementation we want to use). Decoupling the service layer from the other layers of your application is Spring AS’ primary objective.

Beyond that, you are free to implement the design patterns that you like, or that best fit the requirements of your application and the makeup of your development team. For example, you may prefer a design pattern where the view doesn’t have a reference to a service or controller (even if that reference is defined as an interface).


One additional benefit of using a Mock Service is that I can host the application configured with the MockService and let you play with it without having to worry about my database.

Click here to run the application. (View Source is enabled)

Installation instructions

NOTE: If you already installed the application provided with part 2 of this series, you can skip steps 1, 3, 4, 5 and 6.

  1. Install the BlazeDS turnkey server. (To be clear: you don’t have to use BlazeDS to use Spring ActionScript… That’s just what this sample is using.)
  2. Download insyncspringas3.zip, and unzip it on your local file system.
  3. Copy insyncspringas3/java/classes/insync to blazeds/tomcat/webapps/samples/WEB-INF/classes/insync.
  4. Add the following destination to blazeds/tomcat/webapps/samples/WEB-INF/flex/remoting-config.xml:
  5. <destination id="contacts">
            <properties>
                <source>insync.dao.ContactDAO</source>
                <scope>application</scope>
    
            </properties>
    </destination>
    

  6. Copy insyncspringas3/sampledb/insync to blazeds/sampledb/insync
  7. Edit server.properties in blazeds/sampledb, and modify the file as follows to add the insync database to the startup procedure.

    server.database.0=file:flexdemodb/flexdemodb
    server.dbname.0=flexdemodb
    server.database.1=file:insync/insync
    server.dbname.1=insync
    server.port=9002
    server.silent=true
    server.trace=false
    

  8. Start the database (startdb.bat or startdb.sh)
  9. Start BlazeDS
  10. In Flex Builder, create a new Flex project called insyncspringas3. You don’t have to select any “Application server type”.
  11. Copy spring-actionscript.swc and as3reflect.swc from insyncspringas3/flex/lib to the lib directory of your project
  12. Copy the files and folders from insyncspringas3/flex/src to the src directory of your project
  13. Open applicationContext.xml and make sure the remoteObject endpoint value matches your server setup.
  14. Run the application

The “Spring ActionScript” Framework – Part 2: Autowiring

from Cristophe Coenraets

In the first part of this series, we looked at how the “Spring ActionScript” framework can help you externalize the configuration and the wiring of your components, and how you can easily obtain configured objects using applicationContext.getObject().

In this second part, we will discuss how you can make these objects available to the views of your application without tightly coupling these views to the framework, and without passing references around through potentially many levels of view containment.

We will build the “Spring ActionScript” version of the InSync contact management application I often use in this blog to explore new technologies. The application has two views: MainView and ContactForm. Both need a reference to a contact RemoteObject to work.

NOTE: This example is intentionally kept simple. In a more partitioned application, you may want to pass a more abstract controller around as opposed to a specific RemoteObject. We will use this approach in part 3.

The views could use applicationContext.getObject() to access their dependencies (in this case the contact RemoteObject), but this approach has a number of problems:

  1. With a dependency on applicationContext, the views would be tightly coupled to the framework.
  2. We would still need to pass a reference to the applicationContext object to the views. This is often solved using the singleton approach which has its own set of problems.

So, instead of the views instantiating or looking up their dependencies, a better approach would be to “inject” these dependencies into the views.

Unlike Swiz, “Spring ActionScript” doesn’t currently have built-in support for an [Autowire] annotation, but Christophe Herreman seems to imply that this feature is coming, and in the meantime, he provides some sample code to support “Spring ActionScript”-powered autowiring in your application. Using this custom code, the inSync application looks like this:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
 applicationComplete="applicationCompleteHandler()">

 <mx:Script>
 <![CDATA[

 import insync.views.MainView;
 import mx.utils.DescribeTypeCacheRecord;
 import mx.utils.DescribeTypeCache;
 import as3reflect.ClassUtils;
 import org.springextensions.actionscript.context.support.FlexXMLApplicationContext;

 private var applicationContext:FlexXMLApplicationContext;

 private function applicationCompleteHandler():void
 {
  applicationContext = new FlexXMLApplicationContext("applicationContext.xml");
  applicationContext.addEventListener(Event.COMPLETE, applicationContextComplete);
  applicationContext.load();
 }

 private function applicationContextComplete(event:Event):void
 {
  systemManager.addEventListener(Event.ADDED, addedEventHandler);
  var mainView:MainView = new MainView();
  addChild(mainView);
 }

 private function addedEventHandler(event:Event):void
 {
  var autowiredObject:Object = event.target;
  trace("Added to display list: " + autowiredObject);
  var typeInfo:DescribeTypeCacheRecord = DescribeTypeCache.describeType(autowiredObject);
  for each (var metaDataNode:XML in typeInfo.typeDescription..metadata)
  {
   if (metaDataNode.attribute("name") == "Autowired")
   {
    var propertyNode:XML = metaDataNode.parent();
    var property:String = propertyNode.@name.toString();
    trace("Found Autowired property: " + property);
    var objectName:String = property;
    var autowireByType:Boolean = true;

    for each (var arg:XML in metaDataNode.arg)
    {
     if (arg.attribute("value") == "byName")
     {
      autowireByType = false;
     }
    }

    if (autowireByType)
    {
     var clazz:Class = ClassUtils.forName(propertyNode.@type.toString());
     var objectNames:Array = applicationContext.getObjectNamesForType(clazz);
     if (objectNames.length == 1)
     {
      objectName = objectNames[0];
     }
    }
    trace("Autowiring: " + property + " in " + autowiredObject);
    autowiredObject[property] = applicationContext.getObject(objectName);
   }
  }
 }

 ]]>

 </mx:Script>

 <mx:Style source="styles.css"/>

</mx:Application>

To be able to inject properties annotated with Autowired, we register as a listener for the ADDED event on systemManager, and introspect each object added to the display list. If the object has [Autowired] properties, those properties are injected (by name or by type) using applicationContext.getObject(objectName). This is also the approach taken by Swiz.

Timing


Before you inject objects into views, you need to make sure the applicationContext.xml file has been loaded and that the objects it defines have been instantiated. To that effect, the applicationContext dispatches an Event.COMPLETE event when it is ready. To make sure all the views of my application can be properly injected (if needed), the strategy I use in this application is to start instantiating the main view only after this event has been triggered. See addChild(mainView) in the applicationContextComplete handler.

The Views

With that infrastructure in place, the two views of the InSync application are easy to write. Their basic setup looks like this:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">

 <mx:Script>
  <![CDATA[

   import mx.rpc.remoting.mxml.RemoteObject;

   [Autowired]
   public var contactRemoteObject:RemoteObject;

  ]]>
 </mx:Script>

</mx:Canvas>

Installation instructions

NOTE: If you already installed the Swiz version of inSync, you can skip steps 1, 3, 4, 5 and 6.


  1. Install the BlazeDS turnkey server. (To be clear: you don’t have to use BlazeDS to use Spring ActionScript… That’s just what this sample is using.)
  2. Download insyncspringas.zip, and unzip it on your local file system.
  3. Copy insyncspringas/java/classes/insync to blazeds/tomcat/webapps/samples/WEB-INF/classes/insync.
  4. Add the following destination to blazeds/tomcat/webapps/samples/WEB-INF/flex/remoting-config.xml:
  5. <destination id="contacts">
    
            <properties>
                <source>insync.dao.ContactDAO</source>
                <scope>application</scope>
            </properties>
    </destination>
    
    

  6. Copy insyncspringas/sampledb/insync to blazeds/sampledb/insync
  7. Edit server.properties in blazeds/sampledb, and modify the file as follows to add the insync database to the startup procedure.

    server.database.0=file:flexdemodb/flexdemodb
    server.dbname.0=flexdemodb
    server.database.1=file:insync/insync
    server.dbname.1=insync
    server.port=9002
    server.silent=true
    server.trace=false
    

  8. Start the database (startdb.bat or startdb.sh)
  9. Start BlazeDS
  10. In Flex Builder, create a new Flex project called insyncspringas. You don’t have to select any “Application server type”.
  11. Copy spring-actionscript.swc and as3reflect.swc from insyncspringas/flex/lib to the lib directory of your project
  12. Copy the files and folders from insyncspringas/flex/src to the src directory of your project
  13. Open applicationContext.xml and make sure the remoteObject endpoint value matches your server setup.
  14. Run the application

Note on Autowiring and Performance

Annotation-based dependency injection is elegant and easy to work with. However, the current implementation that requires a describeType on every object added to the display list has a performance impact. This is maybe something that the Flex framework could help with in the future. For example, we could inject code at compile time to dispatch an AutowireEvent for each Autowired property when a class is instantiated. We would leave it up to the frameworks to provide a specific injection implementation. That would give these frameworks a better event to listen to, and they wouldn’t have to introspect all the objects added to the display list. An alternative would be to have a compiler hook to allow annotation-based code injection at compile time.

In the meantime, you’ll have to identify if the performance impact of the current approach is acceptable in the context of your application. If not, there are ways to improve the basic approach described above:

  • Using Swiz, Aral Balkan uses an autowire property on the views that require autowiring. In the autowiring code, he then uses the hasOwnPropety(”autowiring”) function to identify if the object needs to be autowired before using describeType. This approach still requires some level of introspection on each object added to the display list.
  • Swiz now makes sure it doesn’t perform describeType on classes in the mx packages. This approach could also be added to the code above.
  • Another approach would be for you to programmatically dispatch an AutowireEvent in the initialize event of the views that need to be autowired. This approach is maybe less elegant than a simple annotation, but wouldn’t have the performance impact of introspecting all the objects added to the display list. It would also work for any object in your application, not only the views.