Wednesday, January 30, 2008

Code by convention with Flex and Spring

Some years ago XML configuration was considered as a great way to configure applications. Take for example Struts' struts-config.xml, in the beginning it was like heaven how you could define all your action mappings etc. But as applications grow it gets harder and harder to maintain this bunch of XML.

The point is applications and application frameworks evolve. In modern application frameworks code by convention is becoming an important feature which reduces repetition and increases uniformity and productivity.

Take for example Grails and Ruby on Rails which are build from the ground up with code by convention in mind. For example in Grails a *Controller.groovy class is a automatically a controller and a *Service.groovy class is automatically a service. You do not need any XML configuration.

Back in my holidays in December I tried Adobe's BlazeDS. From Adobe Labs: BlazeDS is the server-based Java remoting and web messaging technology that enables developers to easily connect to back-end distributed data and push data in real-time to Adobe Flex and Adobe AIR applications for more responsive rich Internet application (RIA) experiences.

What this means is that Flex clients can communicate with Java objects deployed on the server. BlazeDS contains a Java Adapter which forms the infrastructure to make this possible. With Jeff Vroom's Spring Integration you can even use Spring beans to communicate with.

The only drawback with the standard Java Adapter (and so also the Spring Integation) is that each Java object you want to call on the server, you need to configure in remoting-config.xml. Guess what? I don't like that! So what I did is I created a JDK5 annotation to autowire Spring beans directly into BlazeDS. It means when you use Spring and JDK5 you can annotate your service classes with a @RemotingDestination annotation, and they get automatically registered in BlazeDS. Some sort of code by convention I would say!

How it works:


  1. Download the @RemotingDestination annotation for Spring supporting files at http://www.adobe.com/cfusion/exchange/index.cfm?event=extensionDetail&loc=en_us&extid=1398018. It also includes Jeff Vroom's Spring Factory.

  2. Expand remotingdestination-annotation.zip

  3. Copy dist\remotingdestination-annotation.jar in the WEB-INF\lib directory of your web application. Instead you could also include the Java sources (located in src) in your project.

  4. Download the Spring framework at http://www.springframework.org/download

  5. Locate spring.jar in the dist directory and copy it in the WEB-INF\lib directory of your web application

  6. In your Flex services-config.xml configure the SpringAutowiringBootstrapService:

    <?xml version="1.0" encoding="UTF-8"?>
    <services-config>

    <services>
    <service-include file-path="remoting-config.xml" />
    <service-include file-path="proxy-config.xml" />
    <service-include file-path="messaging-config.xml" />

    <service id="spring-autowiring-bootstrap-service" class="flex.contrib.services.SpringAutowiringBootstrapService"/>

    <default-channels>
    <channel ref="my-amf"/>
    </default-channels>

    </services>

    ....
    The SpringAutowiringBootstrapService checks all @RemotingDestination annotated classes and registers them within BlazeDS at runtime. It also dynamically creates the Spring Factory.
    Note that the default channel is required when using runtime registration of remoting destinations.

  7. Finally annotate your service classes with the @RemotingDestination annotation:

    package flex.contrib.samples.mortgage;

    import org.springframework.beans.factory.annotation.Autowired;

    import flex.contrib.stereotypes.RemotingDestination;

    @RemotingDestination(destination="mortgageService")
    public class Mortgage {

    @Autowired
    RateFinder rateFinder;

    public void setRateFinder(RateFinder rateFinder) {
    this.rateFinder = rateFinder;
    }

    public double calculate(double amount) {
    int term = 360; // 30 years
    double rate = rateFinder.findRate();
    return (amount*(rate/12)) / (1 - 1 /Math.pow((1 + rate/12), term));
    }

    }

    By default the Spring bean name is used to register the remoting destination. The name can be overridden using the destination attribute of the annotation. The example class uses Spring annotation-based configuration using the @Autowired annotation. In this case the Spring application context would look like:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="flex.contrib.samples"/>
    </beans>


That's it! The Mortgage service will be automatically registered without any XML configuration. The @RemotingDestination annotation allows you to easily expose services within BlazeDS. Together with Spring's annotation-based configuration this provides a productive code by convention experience for writing Flex applications with Spring on the server-side.

For more info about the @RemotingDestination annotation, SpringAutowiringBootstrapService see the Javadocs in docs\api.

Tuesday, January 29, 2008

BlazeDS Test Drive sample in Grails



In my previous blog entry I introduced the Grails Flex Plugin. I got some reactions on this post and one of them was from Alexander Negoda (aka greendog), who had some problems getting BlazeDS Test Drive Sample 5: Updating data to work on Grails.

A good reason for me to implement the sample myself, and write a small tutorial!


  1. I assume you have Grails already installed and know the basics. This plugin requires requires the latest and greatest Grails 1.0-final development build. It is not compatible with Grails 1.0-RC4.

  2. Create a new Grails application by executing grails create-app product from the Grails command line.

  3. From within the product application folder, install the Flex plugin by executing: grails install-plugin flex
    All Flex libraries and configuration files will be copied into your application's web-app folder.

  4. Create the Product domain class by executing: grails create-domain-class product

  5. Open Product.groovy and add properties so your domain class looks like:

    class Product {
    String name
    String description
    String image
    String category
    Double price
    Integer qtyInStock
    }

  6. Create the Product service by executing: grails create-service product

  7. Open ProductService.groovy and change the class so it looks like:

    class ProductService {

    static expose = ['flex-remoting']

    def getProducts() {
    return Product.list();
    }

    def update(Product product) {
    def p = Product.get(product.id)
    if (p) {
    p.properties = product.properties
    p.save()
    }
    }

    }

    Notice that we expose the service as a flex-remoting service.

  8. Now we are finished with the domain and service layer and we can focus on the Flex front-end. Within the application's web-app folder create a file called main.mxml and add the following content:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" layout="horizontal"
    creationComplete="srv.getProducts()" viewSourceURL="srcview/index.html">

    <mx:RemoteObject id="srv" destination="productService"/>

    <mx:Panel title="Catalog" width="100%" height="100%">
    <mx:DataGrid id="list" dataProvider="{srv.getProducts.lastResult}" width="100%" height="100%"/>
    </mx:Panel>

    <ProductForm product="{Product(list.selectedItem)}"/>

    </mx:Application>

  9. Within the application's web-app folder create a file called ProductForm.mxml and add the following content:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*"
    title="Details" width="100%" height="100%">

    <Product id="product"
    name="{productName.text}"
    category="{category.text}"
    price="{Number(price.text)}"
    image="{image.text}"
    description="{description.text}"/>

    <mx:RemoteObject id="srv" destination="productService"/>

    <mx:Form width="100%">

    <mx:FormItem label="Name">
    <mx:TextInput id="productName" text="{product.name}"/>
    </mx:FormItem>

    <mx:FormItem label="Category">
    <mx:TextInput id="category" text="{product.category}"/>
    </mx:FormItem>

    <mx:FormItem label="Image">
    <mx:TextInput id="image" text="{product.image}"/>
    </mx:FormItem>

    <mx:FormItem label="Price">
    <mx:TextInput id="price" text="{product.price}"/>
    </mx:FormItem>

    <mx:FormItem label="Description" width="100%">
    <mx:TextArea id="description" text="{product.description}" width="100%" height="100"/>
    </mx:FormItem>

    </mx:Form>

    <mx:ControlBar>
    <mx:Button label="Update" click="srv.update(product);"/>
    </mx:ControlBar>

    </mx:Panel>

  10. Within the application's web-app folder create a file called Product.as and add the following content:

    package
    {
    [Bindable]
    [RemoteClass(alias="Product")]
    public class Product
    {
    public function Product()
    {
    }

    public var id:int;

    public var name:String;

    public var description:String;

    public var image:String;

    public var category:String;

    public var price:Number;

    public var qtyInStock:int;

    }
    }

    Note that the main.mxml, ProductForm.mxml and Product.as files are for 99% a copy from the BlazeDS Test Drive sample. In main.mxml and ProductForm.mxml only the destination id of the RemoteObject was changed. And in Product.as the alias of the RemoteClass and the name of the productId column was changed.

  11. Before we run the example, let's add some data to the embedded HSQLDB database. In your application's BootStrap.groovy class create some records as shown below:

    class BootStrap {

    def init = { servletContext ->
    new Product(name: 'Nokia 6010', category: '6000', image: 'Nokia_6010.gif', price: 99.00, description: 'Easy to use without sacrificing style, the Nokia 6010 phone offers functional voice communication supported by text messaging, multimedia messaging, mobile internet, games and more.', qtyInStock: 21).save()
    new Product(name: 'Nokia 3100 Blue', category: '9000', image: 'Nokia_3100_blue.gif', price: 109.00, description: 'Light up the night with a glow-in-the-dark cover - when it is charged with light you can easily find your phone in the dark. When you get a call, the Nokia 3100 phone flashes in tune with your ringing tone. And when you snap on a Nokia Xpress-on gaming cover, you will get luminescent light effects in time to the gaming action.', qtyInStock: 99).save()
    new Product(name: 'Nokia 3100 Pink', category: '3000', image: 'Nokia_3100_pink.gif', price: 139.00, description: 'Light up the night with a glow-in-the-dark cover - when it is charged with light you can easily find your phone in the dark. When you get a call, the Nokia 3100 phone flashes in tune with your ringing tone. And when you snap on a Nokia Xpress-on gaming cover, you will get luminescent light effects in time to the gaming action.', qtyInStock: 30).save()
    new Product(name: 'Nokia 3120', category: '3000', image: 'Nokia_3120.gif', price: 159.99, description: 'Designed for both business and pleasure, the elegant Nokia 3120 phone offers a pleasing mix of features. Enclosed within its chic, compact body, you will discover the benefits of tri-band compatibility, a color screen, MMS, XHTML browsing, cheerful screensavers, and much more.', qtyInStock: 10).save()
    new Product(name: 'Nokia 3220', category: '3000', image: 'Nokia_3220.gif', price: 199.00, description: 'The Nokia 3220 phone is a fresh new cut on some familiar ideas - animate your MMS messages with cute characters, see the music with lights that flash in time with your ringing tone, download wallpapers and screensavers with matching color schemes for the interface.', qtyInStock: 20).save()
    new Product(name: 'Nokia 3650', category: '3000', image: 'Nokia_3650.gif', price: 200.00, description: 'Messaging is more personal, versatile and fun with the Nokia 3650 camera phone. Capture experiences as soon as you see them and send the photos you take to you friends and family.', qtyInStock: 11).save()
    new Product(name: 'Nokia 6820', category: '6000', image: 'Nokia_6820.gif', price: 299.99, description: 'Messaging just got a whole lot smarter. The Nokia 6820 messaging device puts the tools you need for rich communication - full messaging keyboard, digital camera, mobile email, MMS, SMS, and Instant Messaging - right at your fingertips, in a small, sleek device.', qtyInStock: 8).save()
    new Product(name: 'Nokia 6670', category: '6000', image: 'Nokia_6670.gif', price: 319.99, description: 'Classic business tools meet your creative streak in the Nokia 6670 imaging smartphone. It has a Netfront Web browser with PDF support, document viewer applications for email attachments, a direct printing application, and a megapixel still camera that also shoots up to 10 minutes of video.', qtyInStock: 2).save()
    new Product(name: 'Nokia 6620', category: '6000', image: 'Nokia_6620.gif', price: 329.99, description: 'Shoot a basket. Shoot a movie. Video phones from Nokia... the perfect way to save and share life\u2019s playful moments. Feel connected.', qtyInStock: 10).save()
    new Product(name: 'Nokia 3230 Silver', category: '3000', image: 'Nokia_3230_black.gif', price: 500.00, description: 'Get creative with the Nokia 3230 smartphone. Create your own ringing tones, print your mobile images, play multiplayer games over a wireless Bluetooth connection, and browse HTML and xHTML Web pages. ', qtyInStock: 10).save()
    new Product(name: 'Nokia 6680', category: '6000', image: 'Nokia_6680.gif', price: 222.00, description: 'The Nokia 6680 is an imaging smartphone that', qtyInStock: 36).save()
    new Product(name: 'Nokia 6630', category: '6000', image: 'Nokia_6630.gif', price: 379.00, description: 'The Nokia 6630 imaging smartphone is a 1.3 megapixel digital imaging device (1.3 megapixel camera sensor, effective resolution 1.23 megapixels for image capture, image size 1280 x 960 pixels).', qtyInStock: 8).save()
    new Product(name: 'Nokia 7610 Black', category: '7000', image: 'Nokia_7610_black.gif', price: 450.00, description: 'The Nokia 7610 imaging phone with its sleek, compact design stands out in any crowd. Cut a cleaner profile with a megapixel camera and 4x digital zoom. Quality prints are all the proof you need of your cutting edge savvy.', qtyInStock: 20).save()
    new Product(name: 'Nokia 7610 White', category: '7000', image: 'Nokia_7610_white.gif', price: 399.99, description: 'The Nokia 7610 imaging phone with its sleek, compact design stands out in any crowd. Cut a cleaner profile with a megapixel camera and 4x digital zoom. Quality prints are all the proof you need of your cutting edge savvy.', qtyInStock: 7).save()
    new Product(name: 'Nokia 6680', category: '6000', image: 'Nokia_6680.gif', price: 219.00, description: 'The Nokia 6680 is an imaging smartphone.', qtyInStock: 15).save()
    new Product(name: 'Nokia 9300', category: '9000', image: 'Nokia_9300_close.gif', price: 599.00, description: 'The Nokia 9300 combines popular voice communication features with important productivity applications in one well-appointed device. Now the tools you need to stay in touch and on top of schedules, email, news, and messages are conveniently at your fingertips.', qtyInStock: 26).save()
    new Product(name: 'Nokia 9500', category: '9000', image: 'Nokia_9500_close.gif', price: 799.99, description: 'Fast data connectivity with Wireless LAN. Browse the Internet in full color, on a wide, easy-to-view screen. Work with office documents not just email with attachments and memos, but presentations and databases too.', qtyInStock: 54).save()
    new Product(name: 'Nokia N90', category: '9000', image: 'Nokia_N90.gif', price: 499.00, description: 'Twist and shoot. It is a pro-photo taker. A personal video-maker. Complete with Carl Zeiss Optics for crisp, bright images you can view, edit, print and share. Meet the Nokia N90.', qtyInStock: 12).save()
    }
    def destroy = {
    }
    }

  12. Now run the application by executing grails run-app and when started open a browser and navigate to http://localhost:8080/product/main.mxml





Now enjoy and look at the list of products retrieved during startup of the application. Navigate trough the product list and change the properties on the right side. Pushing the Update button will update the record in the database.

Note that updating causes a long stacktrace in the console window, but the update succeeds anyway. It's a StackOverflowError within HSQLDB; I tested the code on a MySQL database and then there is no error thrown.

Sunday, January 20, 2008

Flex on Grails: Introducing the Grails Flex plugin



I haven't touched this blog for the past months as life has been very busy since my son was born. But now it's time again, as I released the Grails Flex Plugin today.

With the plugin Grails services can be exposed as RPC remoting destinations within BlazeDS - Adobe's server-based Java remoting and messaging technology. These remoting destinations can be used in Flex rich internet applications to communicate with the server to restrieve or send data.

Great thing is you can use all nice features from Grails like reloading, GORM etc. and have a real RIA application on the front-end.

See Grails Flex Plugin for more information and a simple usage example.