Affichage des articles dont le libellé est Grails. Afficher tous les articles
Affichage des articles dont le libellé est Grails. Afficher tous les articles

mardi 6 juillet 2010

Flex / Grails CRUD application with GraniteDS, reloaded

Now with Flex 4, Spring Security 3, data push...


It's been almost one year since the last blog entry about the gdsflex plugin. We have just released the version 0.8 of the plugin that brings compatibility with all new releases of the various technologies involved: Grails 1.2/1.3, Flex 4, Spring Security 3... It's time to update the tutorial for this whole new stack.

For the purpose of this article, I'll just write the same boring book application that I've demonstrated before, and simply add additional capabilities: security and data push.

The first important thing before starting is that you now need to install a Flex SDK somewhere and set the FLEX_HOME environment variable to point to its path. It you want to use a Flex 4, that will look like FLEX_HOME = /home/will/flex_sdk_4.
Older versions of the plugin used to embark a full Flex SDK for ease of use, but using FLEX_HOME allows for easier upgrades of the Flex SDK and is more in line with the behaviour of other Grails plugins.

You will also need Grails 1.3.1+ installed and set up (the plugin also works with Grails 1.2.2+ but that could need some minor adjustments in the following steps). Now type from the command line :

grails create-app bookmanager

cd bookmanager

grails install-plugin gdsflex

Part 1 : Define the domain classes and generate a scaffolded Flex application


Just as before, the core of the application is centered on Grails, and more precisely on the domain classes, following usual Grails domain-driven style. Once again we'll start by an Author class.

grails create-domain-class Author

grails-app/domain/bookmanager/Author.groovy
package bookmanager

class Author {

static constraints = {
name(size:0..50, blank:false)
birthDate(format:'DD/MM/YYYY')
picture(nullable:true, widget:'image')
}

String uid

String name

Date birthDate

java.sql.Blob picture
}


And the corresponding controller, here using scaffolding for simplicity :
grails create-controller Author

grails-app/controllers/bookmanager/AuthorController.groovy
package bookmanager

@org.granite.tide.annotations.TideEnabled
class AuthorController {

def index = { redirect(action:list, params:params) }

def scaffold = Author
}


This is not much different from the previous blog examples. Just notice how Grails 1.3 automatically puts domain classes and controllers in a package with the same name than the application.
As always the TideEnabled annotation exposes the controller to the Flex client.

Now we can generate and compile the Flex application :
grails generate-flex-app

grails mxmlc

grails run-app

And go to http://localhost:8080/bookmanager/bookmanager.swf.

You can see a basic working Flex application that allows to create and edit authors. For now the generated app still uses Flex 3 Halo components and not Flex 4 Spark components, but it can perfectly be compiled and run with the Flex 4 SDK.
Future releases of the plugin will generate a Flex 4 Spark application.



What has happened ?
The generate-flex-app script has generated a basic mxml that handles CRUD operations using the Tide UI builder library in grails-app/view/flex/bookmanager.mxml.
The mxmlc script triggers the Flex compiler to build the application swf in grails-app/views/swf.

The Grails server is now started from this terminal, open a new terminal/command line editor to type the next commands.

Now let's add a couple of other classes with associations.
grails create-domain-class Book

grails create-domain-class Chapter

grails create-controller Book

grails-app/domain/bookmanager/Book.groovy
package bookmanager

class Book {

static constraints = {
title(size:0..100, blank:false)
category(inList:["Fiction", "Non-fiction", "Biography"])
author()
description(size:0..2000, widget:"textArea")
chapters(editable:true)
}

String uid

String title

String category

Author author

Set chapters

String description

static hasMany = [ chapters:Chapter ]
static mapping = {
author fetch:"join"
chapters cascade:"all-delete-orphan"
}
}

grails-app/domain/bookmanager/Chapter.groovy
package bookmanager

class Chapter {

static constraints = {
title(size:0..50, blank:false)
}

String uid

Book book

String title

String summary

static mapping = {
book fetch:"join"
}
}

grails-app/controllers/bookmanager/BookController.groovy
package bookmanager

@org.granite.tide.annotations.TideEnabled
class BookController {

def index = { redirect(action:list, params:params) }

def scaffold = Book
}

An important thing is that ManyToOne associations must be marked fetch:"join" or lazy:false. GraniteDS does not support transparent lazy loading for single ended associations, and most likely never will (it would generate unacceptable network and database traffic, one http request and one SQL query for each uninitialized entity). Lazy collections are supported, so OneToMany and ManyToMany associations do not have to be marked lazy:false.

Once the domain classes written, we can generate their Flex ActionScript 3 equivalents with :
grails gas3


An add a Book entry to the main menu in grails-app/view/flex/bookmanager.mxml.
...
<mx:LinkButton label="Books" width="100%" textAlign="left"
click="mainStack.selectedChild = bookUI" />
...
<ui:EntityUI id="bookUI"
entityClass="{Book}"
width="100%" height="100%"/>
...


I won't get into much details about the EntityUI stuff. It comes from the built-in Tide UI builder library that enables the dynamic client-side scaffolding.
For real applications, you should write your own Flex UI to obtain a really rich and usable interface. The auto generated application can be mostly seen as a quick and dirty prototype.

We can now create and edit books, and see how many-to-one and one-to-many associations are displayed in the generated application.

If you eventually tried to add chapters, you will see that it does not work. There are indeed two minor changes to do manually in the AS3 classes to help the UI builder make the link between the associated oneToMany entities: initialize the Chapters collection in the Book.as class, and add a constructor in Chapter.as :

grails-app/views/flex/bookmanager/Book.as
public function Book():void {
chapters = new ArrayCollection();
}

grails-app/views/flex/bookmanager/Chapter.as
public function Chapter(book:Book = null):void {
this.book = book;
}


Once these changes are made, just refresh the browser and check that you can add chapters to your books. Notice how the Gas3 generator and the Flex compiler have been triggered automatically in the background in the running Grails window.


Part 2 : Securing the application


In a real application, it would required that only authenticated users can access enterprise data. Since 0.8, the gdsflex plugin supports the spring-security-core plugin that integrates with Spring Security 3.

Stop the Grails server, and install the plugin with :
grails install-plugin spring-security-core


Then generate the security domain classes Person and Authority (I don't know why, other class names have not worked with the spring-security-core 0.4.1) :
grails s2-quickstart bookmanager Person Authority


The security plugin has generated 3 domain classes : Person, Authority and PersonAuthority. As we have not configured a real database, we are simply going to add a bootstrap script to create default users and roles for our application.

grails-app/conf/Bootstrap.groovy
import bookmanager.Person
import bookmanager.Authority
import bookmanager.PersonAuthority

class BootStrap {

def springSecurityService

def init = { servletContext ->

def user_admin = new Person(username:"admin",
password: springSecurityService.encodePassword("admin"),
enabled:true).save()

def user_user = new Person(username:"user",
password: springSecurityService.encodePassword("user"),
enabled:true).save()

def role_admin = new Authority(description:"Admin",
authority:"ROLE_ADMIN").save()
def role_user = new Authority(description:"User",
authority:"ROLE_USER").save()

new PersonAuthority(person:user_admin, authority:role_admin).save()
new PersonAuthority(person:user_user, authority:role_user).save()
}

def destroy = {
}
}

Now we need to secure access to the application and add a login page. We will make use of the built-in Tide identity component and define a ViewStack to display/hide the login page depending on the current authentication state.

Here's the relevant code to add to grails-app/views/flex/bookmanager.mxml :
<mx:Application
...

import org.granite.tide.spring.Identity;
import org.granite.tide.events.TideResultEvent;
import org.granite.tide.events.TideFaultEvent;

...

[Bindable] [Inject]
public var identity:Identity;

[Bindable]
private var message:String;

private function loginResult(event:TideResultEvent):void {
message = "";
}

private function loginFault(event:TideFaultEvent):void {
message = event.fault.faultString;
}

...

<mx:ViewStack id="appView" selectedIndex="{identity.loggedIn ? 1 : 0}" width="100%" height="100%">
<mx:VBox width="100%" height="100%" verticalAlign="middle" horizontalAlign="center">
<mx:Panel title="Login"
horizontalAlign="center"
verticalGap="0" paddingTop="8" paddingBottom="8"
xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:Form>
<mx:FormItem label="Username">
<mx:TextInput id="username"/>
</mx:FormItem>
<mx:FormItem label="Password">
<mx:TextInput id="password" displayAsPassword="true"
enter="identity.login(username.text, password.text, loginResult, loginFault);"/>
</mx:FormItem>
</mx:Form>

<mx:Label text="{message}"/>

<mx:Button label="Login"
click="identity.login(username.text, password.text, loginResult, loginFault);"/>
</mx:Panel>
</mx:VBox>

<mx:VBox id="mainUI" width="100%" height="100%">
<mx:ApplicationControlBar id="acb" width="100%">
<mx:Label text="GraniteDS / Grails generated application" fontSize="18" fontWeight="bold" color="#f0f0f0"/>
<mx:Spacer width="100%"/>
<mx:Label text="Logged in as {identity.username}"/>
<mx:Button label="Logout" click="identity.logout()"/>
</mx:ApplicationControlBar>

...
</mx:VBox>

</mx:ViewStack>
</mx:Application>


There is nothing very sophisticated here, just notice how we make use of the identity.loggedIn property to display the correct UI and identity.login/identity.logout to trigger user login/logout.

You can restart the application, and check that you can log in with the admin/admin and user/user usernames and passwords combinations (and obviously not with others).

Note that we could also have relied on the default html login page of the spring-security-core plugin to secure the application and protect access to the swf itself. That would be the way to go to enable OpenID authentication or any other Web-based authentication model. In this case, you don't necessarily have to add a Flex login page, but you have to indicate to Flex that it has to retrieve the authentication state at startup with something like :
<mx:Application creationComplete="identity.isLoggedIn()" ... />


This can also be used to allow the authentication state to be restored after a browser page refresh.

We can now try to add more advanced authorization filtering. Let's say that we want to forbid deletion of authors by non-administrator users. This requires a few steps :

1. Enable authorization support in grails-app/conf/GraniteDSConfig.groovy


graniteConfig {
springSecurityAuthorizationEnabled = true
springSecurityIdentityClass = org.granite.tide.spring.security.Identity3
}


This tells GraniteDS to enable integration with Spring Security authorizations and defines the Identity3 integration class for Spring Security 3 (the one used in the spring-security-core plugin).

2. Use the Identity component


Usually you will use the Identity component with Flex data binding on component properties such as visible or enabled to hide/disable parts of the UI depending on the user access rights.
For example this will define a button that is displayed only to administrator users :
<mx:Button label="Delete" visible="{identity.ifAllGranted('ROLE_ADMIN')}" includeInLayout="{identity.ifAllGranted('ROLE_ADMIN')}"/>


In our example, it will be a bit more tricky because we use the automatic Tide UI builder. We have to override the default mxml for authors and manually set the data binding for the visible and includeInLayout properties.

grails-app/views/flex/AuthorEdit.mxml
<ui:EntityEdit 
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:ui="org.granite.tide.uibuilder.*"
xmlns="*">
<mx:Script>
import mx.binding.utils.BindingUtils;
import org.granite.tide.spring.Identity;

[Inject]
public var identity:Identity;

protected override function layoutForm():void {
super.layoutForm();

BindingUtils.bindProperty(this.buttonBar.getChildAt(1), "includeInLayout", identity,
{ name: 'ifAllGranted', getter: function(identity:Identity):Boolean { return identity.ifAllGranted('ROLE_ADMIN'); } }
);
BindingUtils.bindProperty(this.buttonBar.getChildAt(1), "visible", identity,
{ name: 'ifAllGranted', getter: function(identity:Identity):Boolean { return identity.ifAllGranted('ROLE_ADMIN'); } }
);
}
</mx:Script>
</ui:EntityEdit>


And the override in the main mxml :

grails-app/views/flex/bookmanager.mxml
...            
Spring.getInstance().addComponentWithFactory("bookmanager.author.entityEdit", AuthorEdit,
{ entityClass: Author }, true, true, Tide.RESTRICT_UNKNOWN, null, false);
...


Now if you restart the application, you can check that the Delete button is displayed only when you are logged in as administrator.
Note that user authorizations are retrieved from the server the first time, but are then cached locally, so it's not expensive to add as many ifAllGranted as you wish in the application.


Part 3 : Enabling data push for the application


We can now securely display, create, edit and delete our data. The last advanced feature of GraniteDS that I will show in this post is the data push that allows data updates to be propagated in near real-time to other users.
Once again it involves a few steps :

1. Configure a Tomcat NIO connector


The default Tomcat configuration included in Grails 1.2+ does not use a NIO connector. Using the NIO connector is recommended for GraniteDS data push to provide the better scalability.
It is possible to use the default connector during development but the NIO configuration is interesting (as an aside, thanks to John Fletcher for having taken the time to dig this from the deep of the Grails mailing list, see here: http://www.saltwebsites.com/blog/grails-gds-gravity-guide).
In scripts/_Events.groovy, add this :
import org.apache.catalina.connector.Connector;

eventConfigureTomcat = { tomcat ->

def nioConnector = new Connector("org.apache.coyote.http11.Http11NioProtocol")
nioConnector.port = 8081
nioConnector.setProperty("redirectPort", "8443")
nioConnector.setProperty("protocol", "HTTP/1.1")
nioConnector.setProperty("enableLookups", "false")

tomcat.service.addConnector nioConnector
}


Then you will have to access the application with http://localhost:8081 instead of the standard port 8080.

Note that for production use, it is highly recommended to use the native APR connector, see in the Tomcat documentation here for more information.

2. Enable Gravity/push support in grails-app/conf/GraniteDSConfig.groovy


graniteConfig {
springSecurityAuthorizationEnabled = true
springSecurityIdentityClass = org.granite.tide.spring.security.Identity3

gravityEnabled = true
dataDispatchEnabled = true
}

3. Define a Gravity channel and destination


Then we have to define a Gravity channel and topic in the services-config.xml Flex configuration:

web-app/WEB-INF/flex/services-config.xml:
<services-config>
<services>
<service id="granite-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">

<destination id="spring">
<channels>
<channel ref="my-graniteamf"/>
</channels>
<properties>
<factory>tideSpringFactory</factory>
<persistenceManagerBeanName>tidePersistenceManager</persistenceManagerBeanName>
</properties>
</destination>
</service>

<service id="gravity-service"
class="flex.messaging.services.MessagingService"
messageTypes="flex.messaging.messages.AsyncMessage">
<adapters>
<adapter-definition id="simple" class="org.granite.gravity.adapters.SimpleServiceAdapter"/>
</adapters>

<destination id="bookManagerTopic">
<properties>
<session-selector>true</session-selector>
</properties>
<channels>
<channel ref="my-gravityamf"/>
</channels>
<adapter ref="simple"/>
</destination>
</service>
</services>

<factories>
<factory id="tideSpringFactory" class="org.granite.tide.spring.SpringServiceFactory">
</factory>
</factories>

<!--
! Declares my-graniteamf channel.
!-->
<channels>
<channel-definition id="my-graniteamf" class="mx.messaging.channels.AMFChannel">
<endpoint
uri="http://{server.name}:{server.port}/{context.root}/graniteamf/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>

<channel-definition id="my-gravityamf" class="org.granite.gravity.channels.GravityChannel">
<endpoint
uri="http://{server.name}:{server.port}/{context.root}/gravityamf/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
</services-config>

4. Configure the Flex client application to use the topic


grails-app/view/flex/bookmanager.mxml
import org.granite.tide.data.DataObserver;

// Register a data observer component with the name of the topic
Spring.getInstance().addComponent("bookManagerTopic", DataObserver, false, true);
// Binds the subscribe and unsubscribe methods of the component to the application login/logout events
Spring.getInstance().addEventObserver("org.granite.tide.login", "bookManagerTopic", "subscribe");
Spring.getInstance().addEventObserver("org.granite.tide.logout", "bookManagerTopic", "unsubscribe");

5. Configure the controllers to handle dispatch of data updates


Just add the following annotation to all the controllers of the application :
@DataEnabled(topic="bookManagerTopic", params=ObserveAllPublishAll.class, publish=DataEnabled.PublishMode.ON_SUCCESS)


This references a parameter class that allows filtering of which update events can be dispatched. Here we will use an extremely simple filter class that filters nothing :

src/groovy/ObserveAllPublishAll.groovy
import org.granite.tide.data.DataObserveParams;
import org.granite.tide.data.DataPublishParams;
import org.granite.tide.data.DataTopicParams;

public class ObserveAllPublishAll implements DataTopicParams {

public void observes(DataObserveParams params) {
// Define key/value pairs that the client is able to observe
// Will be used to construct the topic selector for the client calling the component
}

public void publishes(DataPublishParams params, Object entity) {
// Define key/value pairs to add in the published message headers
// Will be used to match against the topic selector of the subscribed clients
}
}

Part 4 : Deploy all this as a war in Tomcat


Just run :

grails war

Then rename the generated bookmanager-0.1.war to bookmanager.war and copy it to the Tomcat webapps folder.
Ensure you have correctly installed the native APR connector, start Tomcat, and browse http://localhost:8080/bookmanager/bookmanager.swf, et voilà !


I will stop here for this article. We could go a bit further by adding conflict handling, or per-instance authorizations, but I let this for a future post.
Hopefully this article has shown the range of capabilities of the plugin and of the underlying GraniteDS Flex/Java integration library.

As always, don't hesitate to experiment all this and give your feedback.

mardi 25 août 2009

Build a Flex CRUD application in 15 minutes with the Grails GraniteDS plugin

A few month ago, I've shown how to build a simple CRUD application with Flex, Grails and the GraniteDS plugin. That was not overly complex but there was still room for improvement in the quantity of code that was needed to make things work.
Grails itself provides a feature called scaffolding that allows to build extremely easily a simple working application skeleton from constraints defined on the domain classes.

Doing the same with Flex is a bit more complex because there are two runtimes involved: the Flash client and the Java server. There are thus three main possible options:

  • Generation and compilation of static Flex code on the server. Its advantages are static typing and full customizability. Its drawbacks are the quantity of code to maintain (even if it's generated, it's still here) and a relative difficulty to do incremental development (once the generated app has been customized, it's harder to update it when the source model changes). This is the option taken by the GFS plugin for example.

  • Dynamic generation of domain metadata on the server used by a Flex framework to build the UI dynamically. This has the big advantage of being completely driven by the server, so changes on the domain classes are immediately visible on the UI without requiring a recompilation of the client, and moreover there is relatively little client code. The corresponding drawback is that the generated application is not typed (client side data would be represented as XML for example) and is more difficult to maintain and customize.

  • Generation of a static Flex domain model and dynamic generation of the Flex UI. This is the approach taken by the GraniteDS plugin, its advantages are a limited amount of client code and a strongly typed client application. The drawbacks are that it's less dynamic than option 2 (changes on the Grails domain class require a recompilation of the Flex application), and less easily customizable than option 1.


  • After this short introduction, let's see how this works with the latest release of gdsflex (0.7.1).

    First we create a new Grails application, once again a book manager (maybe next time I'll find some more original example). Assuming you have grails 1.1.1 installed, type from the command line :

    grails create-app gdsexample2

    cd gdsexample2

    grails install-plugin gdsflex


    As before, the main part is to define the domain model. Let's start this time by a simple Author class.

    grails create-domain-class com.myapp.Author

    Author.groovy

    package com.myapp

    class Author {

    static constraints = {
    name(size:0..50, blank:false)
    birthDate(format:'DD/MM/YYYY')
    picture(nullable:true, widget:'image')
    }

    String uid

    String name

    Date birthDate

    java.sql.Blob picture
    }

    This is a relatively simple domain class, you can notice the constraints that drive Grails scaffolding. So let's add a simple controller :

    grails create-controller com.myapp.Author

    AuthorController.groovy

    package com.myapp

    @org.granite.tide.annotations.TideEnabled
    class AuthorController {

    def index = { redirect(action:list, params:params) }

    def scaffold = Author
    }

    Then run :

    grails generate-flex-app

    grails mxmlc

    grails run-app

    And go to http://localhost:8080/gdsexample2/gdsexample2.swf.

    You should have a basic working Flex application that allows to create and edit authors. So what happened here :

  • The gas3 generator has generated ActionScript 3 classes for each domain class (as that was the case before), this time incuding the existing Grails constraints.

  • A new scaffolding template for controllers has been installed in src/templates/scaffolding that includes the necessary actions for the Flex application.

  • The Flex UI builder classes (org.granite.tide.uibuilder) have been installed in grails-app/views/flex.

  • A main mxml has been generated in grails-app/views/flex/gdsexample2.mxml that includes a menu to access all domain classes.


  • This is a simple example, but we can see how the constraints have been used : the Grails constraints in Author.groovy have been translated to the following block in Author.as :

    public static const meta_constraints:Array = [
    { property: "name",
    blank: "false", size: "0..50"
    },
    { property: "birthDate",
    format: "DD/MM/YYYY"
    },
    { property: "picture",
    widget: "image"
    }
    ]

    Even if you don't want to use the GraniteDS UI builder framework, you could still use these as3 constraints from any custom framework.

    You can check that the blank and size constraints have been mapped to Flex client-side validators, and that the date format has been used in the author table. The 'image' widget is simply a component that appears in edit mode and allows to upload an image from Flex.

    Now that we have seen a basic domain class, let's see how it works with associations.

    grails create-domain-class com.myapp.Book

    grails create-domain-class com.myapp.Chapter

    grails create-controller com.myapp.Book

    Book.groovy

    package com.myapp

    class Book {

    static constraints = {
    title(size:0..100, blank:false)
    category(inList:["Fiction", "Non-fiction", "Biography"])
    author()
    description(size:0..2000, widget:"textArea")
    chapters()
    }

    String uid

    String title

    String category

    Author author

    Set chapters

    String description

    static hasMany = [ chapters:Chapter ]
    static mapping = {
    author fetch:"join"
    chapters cascade:"all-delete-orphan"
    }
    }

    Chapter.groovy

    package com.myapp

    class Chapter {

    static constraints = {
    title(size:0..50, blank:false)
    }

    String uid

    Book book

    String title

    String summary

    static mapping = {
    book fetch:"join"
    }
    }

    BookController.groovy

    package com.myapp

    @org.granite.tide.annotations.TideEnabled
    class BookController {

    def index = { redirect(action:list, params:params) }

    def scaffold = Book
    }

    An important thing is that ManyToOne associations must be marked fetch:"join" or lazy:false. GraniteDS does not support transparent lazy loading for single ended associations, and most likely never will (it would generate unacceptable network and database traffic, one http request and one SQL query for each uninitialized entity). Lazy collections are supported, so OneToMany and ManyToMany associations do not have to be marked lazy:false.

    Let's regenerate the Flex application to add the Book menu to the main mxml.

    grails generate-flex-app

    grails mxmlc


    You can notice that the main mxml has been overwritten. This is quite annoying if you made some changes to the generated file but the previous file is backuped and in general you would have added the entity manually in the mxml. If you have a look to the mxml, only two lines have been added for the new entity :

    <mx:LinkButton label="Books" width="100%" textAlign="left"
    click="mainStack.selectedChild = bookUI" />


    <ui:EntityUI id="bookUI"
    entityClass="{Book}"
    width="100%" height="100%"/>

    The EntityUI is a Flex component and can just be used anywhere in a Flex application (provided the Tide framework initialization has been done correctly).

    We can now create and edit books, and see how many-to-one and one-to-many associations are displayed in the generated application.

    As you eventually tried to add chapters, you will see that it does not work. There are indeed two minor changes to do manually in the AS3 classes to help the UI builder make the link between the associated oneToMany entities: initialize the Chapters collection in the Book.as class, and add a constructor in Chapter.as :

    public function Book():void {
    chapters = new ArrayCollection();
    }


    public function Chapter(book:Book = null):void {
    this.book = book;
    }

    Once these changes are made, just refresh the browser and check that you can add chapters to your books.

    Now we're almost finished, we'll just add a final improvement to this simple app:

    grails html-wrapper

    As the command name indicates, this will generate a html wrapper and allow the application to use Flex deep linking, and to use Grails url mapping on the flex application now located at flexindex.gsp.

    Now you can go to http://localhost:8080/gdsexample2/flexindex.gsp#book/list to access the book list directly.

    The documentation on the plugin page gives more information on what constraints are supported by the builder and how to override the behaviour of the generated application. Anyway the sources of the UI builder are also present in the grails-app/views/flex folder and can be used as a template to build your customized UI.

    It should be easy to add security and data push to the generated application by following the previous blog entry here.

    Hopefully this new feature of the gdsflex Grails plugin is a useful tool to get started quickly with Flex and GraniteDS and to quickly build prototype applications.
    As always, feedback and comments are welcome.

    lundi 6 avril 2009

    Building a Flex multi-tab CRUD application : adding security and server push

    The first part of this article has described how to build the main application. The goal of this second part is to demonstrate how you can easily add security and collaborative updates to this existing application with the gdsflex 0.3 Grails plugin.

    Before anything, we need to upgrade the previous project to the latest gdsflex plugin release 0.3 with :
    grails install-plugin gdsflex

    The usual way of adding security to a Grails application is to use the Acegi/Spring security Grails plugin. So now we have to setup this plugin.
    grails install-plugin acegi

    grails create-auth-domains User Role Requestmap

    grails generate-manager

    This will install the plugin and generate a set of gsp pages to create roles and users. Now browse http://localhost:8080/gdsexample/role/create, and create 2 roles ROLE_ADMIN and ROLE_USER. Then http://localhost:8080/gdsexample/user/create, and create 2 users admin and user, each one with one of the previously created roles. The names of the roles are important for Spring security and have to start with 'ROLE_'.

    We also have to enable the Spring security integration in the GraniteDS config file:

    web-app/WEB-INF/granite/granite-config.xml, uncomment or add the following line:
    <security type="org.granite.messaging.service.security.SpringSecurityService"/>

    Once this setup is done, we have to define a login page in the Flex application. To do this, we will simply add a ViewStack in the main MXML that will display either the login page or the application depending on the user login state. This ViewStack will be bound to the Tide Identity component that handles user authentication :

    grails-app/views/GDSExample.mxml :
    <?xml version="1.0" encoding="utf-8"?>

    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    layout="vertical"
    backgroundGradientColors="[#0e2e7d, #6479ab]"
    preinitialize="Spring.getInstance().initApplication()">

    <mx:Script>
    <![CDATA[
    import org.granite.tide.spring.Spring;
    import org.granite.tide.validators.ValidatorExceptionHandler;
    import org.granite.tide.spring.Identity;
    import org.granite.tide.events.TideResultEvent;
    import org.granite.tide.events.TideFaultEvent;

    Spring.getInstance().addExceptionHandler(ValidatorExceptionHandler);

    [Bindable] [In]
    public var identity:Identity;


    [Bindable]
    private var message:String;

    private function loginResult(event:TideResultEvent):void {
    message = "";
    }

    private function loginFault(event:TideFaultEvent):void {
    message = event.fault.faultString;
    }
    ]]>
    </mx:Script>

    <mx:ViewStack id="appView" selectedIndex="{identity.loggedIn ? 1 : 0}" width="100%" height="100%">

    <mx:VBox width="100%" height="100%" verticalAlign="middle" horizontalAlign="center" >
    <mx:Panel title="Login"
    horizontalAlign="center"
    verticalGap="0" paddingTop="8" paddingBottom="8"
    xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:Form>
    <mx:FormItem label="Username">
    <mx:TextInput id="username"/>
    </mx:FormItem>
    <mx:FormItem label="Password">
    <mx:TextInput id="password" displayAsPassword="true"
    enter="identity.login(username.text, password.text, loginResult, loginFault);"/>
    </mx:FormItem>
    </mx:Form>

    <mx:Label text="{message}"/>

    <mx:Button label="Login"
    click="identity.login(username.text, password.text, loginResult, loginFault);"/>
    </mx:Panel>
    </mx:VBox>

    <mx:VBox id="mainUI" width="100%">
    <mx:ApplicationControlBar id="acb" width="100%">
    <mx:Label text="GraniteDS / Grails example" fontSize="18" fontWeight="bold" color="#f0f0f0"/>
    <mx:Button label="Logout" click="identity.logout()"/>
    </mx:ApplicationControlBar>

    <BookUI id="bookUI" width="100%" height="100%"/>
    </mx:VBox>

    </mx:ViewStack>
    </mx:Application>

    As you can see, most of the code we have added is the login form. The authentication is completely handled by the Identity component. Here we make use of its two main methods login / logout and of its loggedIn property that is bindable and is here used to select which view is displayed. We also define a fault handler to display a login error message when the entered credentials are not correct.

    You can check now on http://localhost:8080/gdsexample/GDSExample.swf that only authenticated people can see and change existing data.

    To further refine the security of the application, we could decide that only administrators are allowed to delete books in our database. This can be done server-side with Spring security, but here we'll just see how to modify the UI according to the user access rights. This is not enabled by default in the plugin, we just have to change the plugin settings, and add a GraniteDSConfig.groovy in grails-app/conf:

    grails-app/conf/GraniteDSConfig.groovy :
    graniteConfig {

    springSecurityAuthorizationEnabled = true

    }

    Then we have to make a few changes in the MXML UI to hide the Delete button when the user is not an administrator:

    grails-app/views/BookEdit.mxml :

    import org.granite.tide.spring.Identity;

    [Bindable] [In]
    public var identity:Identity;


    <mx:HBox>
    <mx:Button label="Save" click="save()"/>
    <mx:Button label="Delete"
    enabled="{!create}"
    visible="{identity.ifAllGranted('ROLE_ADMIN')}"
    click="remove()"/>
    <mx:Button label="Cancel" click="cancel()"/>
    </mx:HBox>

    You can check that if you don't log in with a user having the ROLE_ADMIN role, you don't have the Delete button. You should of course also enable server-side Spring security (for example with the @Secured annotation) and define a AccessDeniedExceptionHandler that would catch all security errors to display a uniform message (see the documentation or GDS/Tide/Spring example for more information).
    The Identity component provides 4 bindable boolean methods ifAllGranted, ifNotGranted, ifAnyGranted and hasPermission corresponding to the JSP tags available in the Spring security tag library. The hasPermission is not supported right now by the Grails plugin but it depends on the configuration of Spring security ACLs that itself would probably require a complete article.

    To finish this post, we will add real-time multiuser updates by adding the Gravity push functionality to our application. First we have to enable these functionalities in the plugin settings:

    grails-app/conf/GraniteDSConfig.groovy :
    graniteConfig {

    springSecurityAuthorizationEnabled = true

    gravityEnabled = true

    dataDispatchEnabled = false
    }

    Then we have to define a Gravity channel and topic in the services-config.xml Flex configuration:

    web-app/WEB-INF/flex/services-config.xml:
    <services-config>

    <services>
    <service id="granite-service"
    class="flex.messaging.services.RemotingService"
    messageTypes="flex.messaging.messages.RemotingMessage">

    <destination id="spring">
    <channels>
    <channel ref="my-graniteamf"/>
    </channels>
    <properties>
    <factory>tideSpringFactory</factory>
    <persistenceManagerBeanName>tidePersistenceManager</persistenceManagerBeanName>
    </properties>
    </destination>
    </service>

    <service id="gravity-service"
    class="flex.messaging.services.MessagingService"
    messageTypes="flex.messaging.messages.AsyncMessage">
    <adapters>
    <adapter-definition id="simple" class="org.granite.gravity.adapters.SimpleServiceAdapter"/>
    </adapters>

    <destination id="bookManagerTopic">
    <properties>
    <session-selector>true</session-selector>
    </properties>
    <channels>
    <channel ref="my-gravityamf"/>
    </channels>
    <adapter ref="simple"/>
    </destination>
    </service>
    </services>

    <factories>
    <factory id="tideSpringFactory" class="org.granite.tide.spring.SpringServiceFactory">
    </factory>
    </factories>

    <!--
    ! Declares my-graniteamf channel.
    !-->
    <channels>
    <channel-definition id="my-graniteamf" class="mx.messaging.channels.AMFChannel">
    <endpoint
    uri="http://{server.name}:{server.port}/{context.root}/graniteamf/amf"
    class="flex.messaging.endpoints.AMFEndpoint"/>
    </channel-definition>

    <channel-definition id="my-gravityamf" class="org.granite.gravity.channels.GravityChannel">
    <endpoint
    uri="http://{server.name}:{server.port}/{context.root}/gravityamf/amf"
    class="flex.messaging.endpoints.AMFEndpoint"/>
    </channel-definition>
    </channels>

    </services-config>

    Here we have just added a new channel-definition, gravity-service and topic destination in the configuration, now we have to tell the Flex client and the Grails components to send/receive the data updates on this topic. The server part is relatively simple, we just have to annotate our controllers/services with the DataEnabled annotation:
    @DataEnabled(topic="bookManagerTopic", params=ObserveAllPublishAll.class, publish=DataEnabled.PublishMode.ON_SUCCESS)

    This annotation specifies the topic and has two other important parameters:

    - 'params' defines a publishing selection class. To keep things simple here, we will just write a very simple selection class that allow everyone to observer and publish all updates. For some reason, using Groovy classes in annotations triggers an infinite loop in the Grails compilation, so we will just write a Java class this time:

    src/java/ObserveAllPublishAll.java
    import org.granite.tide.data.DataObserveParams;
    import org.granite.tide.data.DataPublishParams;
    import org.granite.tide.data.DataTopicParams;

    public class ObserveAllPublishAll implements DataTopicParams {

    public void observes(DataObserveParams params) {
    // Define key/value pairs that the client is able to observe
    // Will be used to construct the topic selector for the client calling the component
    }

    public void publishes(DataPublishParams params, Object entity) {
    // Define key/value pairs to add in the published message headers
    // Will be used to match against the topic selector of the subscribed clients
    }
    }

    - 'publish' defines the publishing mode. ON_SUCCESS indicates that all remote calls on annotated components that do not trigger exceptions will dispatch the corresponding entity updates. In general you will want to have more control on the dispatching and use the MANUAL mode to be sure that updates are dispatched only on transaction success and include the dispatch in a Grails interceptor. We could also have used a transactional JMS topic to have a real transactional behaviour. Let's keep it simple for this example.

    On Flex side, you then have to add the data observer component in the main MXML:

    grails-app/views/GDSExample.mxml :
                import org.granite.tide.data.DataObserver;

    // Register a data observer component with the name of the topic
    Spring.getInstance().addComponent("bookManagerTopic", DataObserver);
    // Binds the subscribe and unsubscribe methods of the component to the application login/logout events
    Spring.getInstance().addEventObserver("org.granite.tide.login", "bookManagerTopic", "subscribe");
    Spring.getInstance().addEventObserver("org.granite.tide.logout", "bookManagerTopic", "unsubscribe");

    [In]
    public var bookManagerTopic:DataObserver;

    An interesting thing here is how you can bind a Tide event to any Tide component method, allowing for a complete decoupling between the component and the application.

    Now if you open two browsers on the Flex application (in different http sessions, so for example you can try with one Firefox and one Internet Explorer, or better on two different machines), you can check that updates saved on one browser are (almost) immediately visible on the other.

    If you look at the console, you will most likely see a lot of error stack traces concerning some RetryRequest error. This has something to do with some Grails filter logging the Jetty continuation retry exception. That's not extremely important, and it will be addressed in a later release. These configuration aspects of Gravity and other advanced features will also be improved to avoid having many places where to change configuration and centralize most of it in the GraniteDSConfig.groovy file.


    Now we've seen a simple use of most features of GraniteDS. The complete project is available [here|http://www.graniteds.org/confluence/download/attachments/9371677/gdsexample-0.3.zip] and can be unzipped after having created the project and installed the various plugins (also remember to create roles and user otherwise you won't be able to log in).
    We could have implemented the server side with any other Java server technology supported out-of-the-box by GDS/Tide (JBoss Seam, Spring, EJB3) with minimal change on the Flex side, but the Grails/GraniteDS combination seems a good choice to quickly develop Flex applications with a Java server backend.

    lundi 30 mars 2009

    Building a Flex multi-tab CRUD application with the Grails GraniteDS plugin

    Granite Data Services provides a complete platform for building Flex RIAs with a Java backend. I'm going to show how to build a simple multi-tab CRUD application in a few steps. GraniteDS 2.0 brings a lot of improvements in the Spring integration and now supports Grails with a specific plugin. The Flex/Grails/GraniteDS combination is very efficient and makes building applications quite fast, that's why I will build the backend with Grails. Moreover it makes extremely easy to create and setup the project. We could also have built the backend with Spring or JBoss Seam with very few changes in the Flex application itself.

    We start by creating a new Grails application with the GDSFlex plugin (now at version 0.2), assuming Grails 1.1 is installed somewhere :
    grails create-app gdsexample
    cd gdsexample
    grails install-plugin gdsflex

    The main part of the job is to define our model. We are going to build a very simple book manager, so we will need two Book and Chapter domain classes.
    grails create-domain-class Book
    grails create-domain-class Chapter

    These domain classes are empty for now so let's add a few properties.

    grails-app/domain/Book.groovy
    import javax.persistence.*

    @Entity
    class Book {

    static constraints = {
    title(size:0..100, blank:false)
    }

    @Id
    Long id

    @Version
    Integer version

    String uid

    String title

    String author

    String description

    Set<Chapter> chapters
    static hasMany = [chapters:Chapter]
    static mapping = {
    chapters cascade:"all,delete-orphan"
    }
    }

    grails-app/domain/Chapter.groovy :
    import javax.persistence.*

    @Entity
    class Chapter {

    static constraints = {
    title(size:0..100, blank:false)
    }

    @Id
    Long id

    @Version
    Integer version

    String uid

    Book book

    String title

    String summary
    }

    You can notice that these are not pure GORM entities. GraniteDS requires at least the 3 JPA annotations @Entity, @Id and @Version to be able to correctly generate the AS3 classes. This is the only restriction on the domain classes and all other fields can be defined as for any GORM entity.
    Another particularity of these domain classes is the uid field that will be used as a unique identifier when exchanging data between the Flex, Hibernate and database layers. It's not mandatory to have one but it can save a lot of problems (mainly with new entities created from Flex).

    Now let's try to compile the project :
    grails compile

    You can notice in the console that the Grails compilation has triggered the Gas3 generator and that there are 4 new AS3 classes in grails-app/views: Book.as, BookBase.as, Chapter.as, ChapterBase.as.
    In general you won't even have to think about these classes and you can just use them in your Flex application. If you need to add some behaviour to the Flex model, you can just modify the non 'Base' classes that won't be overwritten by the generator process.

    We need something to show in our Flex application, so we'll simply use Grails scaffolding to get a simple Web UI and populate some data.
    grails create-controller Book

    Edit the controller in grails-app/controllers/BookController.groovy :
    class BookController {

    def index = { redirect(action:list, params:params) }

    static scaffold = Book
    }

    Go to http://localhost:8080/gdsexample/book/list and create a few books.


    To display these books in a Flex application, we will make use of the PagedQuery Flex component that handles data paging and sorting with a server backend. First we need to create the corresponding Grails service that implements a 'find' method:

    grails create-service BookList


    grails-app/services/BookListService.groovy :
    import org.granite.tide.annotations.TideEnabled

    @TideEnabled
    class BookListService {

    boolean transactional = true

    def find(filter, offset, max, sort, order) {
    if (max == 0)
    max = 20

    return [
    firstResult: offset,
    maxResults: max,
    resultCount: Book.count(),
    resultList: Book.list([offset: offset, max: max, sort: sort, order: order ? "desc" : "asc"])
    ]
    }
    }

    The only remarkable thing is the TideEnabled annotation that will tell the GraniteDS Tide library that the component can be called from Flex.

    Next we build the Flex MXML views:

    grails-app/views/GDSExample.mxml:
    <?xml version="1.0" encoding="utf-8"?>

    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    layout="vertical"
    backgroundGradientColors="[#0e2e7d, #6479ab]"
    preinitialize="Spring.getInstance().initApplication()">

    <mx:Script>
    <![CDATA[
    import org.granite.tide.spring.Spring;
    ]]>
    </mx:Script>

    <mx:VBox id="mainUI" width="100%">
    <mx:ApplicationControlBar id="acb" width="100%">
    <mx:Label text="GraniteDS / Grails example" fontSize="18" fontWeight="bold" color="#f0f0f0"/>
    </mx:ApplicationControlBar>
    </mx:VBox>

    <BookUI id="bookUI" width="100%" height="100%"/>
    </mx:Application>

    The important thing here is the preinitialize handler that bootstraps the Tide runtime.

    Here start the interesting things, the MXML component that will display the list.
    Comments in the source code explain the important parts.

    grails-app/views/BookUI.mxml:
    <?xml version="1.0" encoding="utf-8"?>

    <mx:Panel
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    width="100%"
    label="Book Manager"
    creationComplete="list()">

    <!--
    The [Name] metadata indicates that the component has to be managed by Tide.
    This will enable Tide property injections and observer methods.
    As we don't specify a name here, the component will get the name of the Flex component.
    Here the name will be 'bookUI' as defined by the parent MXML.
    -->
    <mx:Metadata>
    [Name]
    </mx:Metadata>

    <mx:Script>
    <![CDATA[
    import org.granite.tide.spring.PagedQuery;

    // This is a property injection definition.
    // The PagedQuery component needs to be linked to a server counterpart with the same name,
    // so here the property is named 'bookListService' as the Grails service.
    // The [In] annotation indicates that the property has to be injected by Tide,
    // meaning that Tide will automatically create a PagedQuery component and bind it to the property.
    // PagedQuery extends the standard Flex ListCollectionView and can be used directly
    // as a data provider for the DataGrid (as shown below).
    [Bindable] [In]
    public var bookListService:PagedQuery;

    // This specifies an observer method that will be called
    // when a Tide event with this name is dispatched. It will be used later.
    [Observer("listBook")]
    public function list():void {
    bookListService.refresh();
    }
    ]]>
    </mx:Script>

    <mx:TabNavigator id="nav" width="100%" height="100%">
    <mx:VBox label="Book List">
    <mx:DataGrid id="dgList" dataProvider="{bookListService}" width="400" height="200">
    <mx:columns>
    <mx:DataGridColumn dataField="title"/>
    <mx:DataGridColumn dataField="author"/>
    </mx:columns>
    </mx:DataGrid>

    <mx:Button label="Refresh" click="list()"/>
    </mx:VBox>
    </mx:TabNavigator>

    </mx:Panel>

    If you go to http://localhost:8080/gdsexample/GDSExample.swf, you will get a first look at the application. Unfortunately that first look will be an ArgumentError popup at startup. What has happened is that the Flex compiler did not include the Book class in the swf and that the serialization of the Book objects failed. You can check that we have no reference to the Person class anywhere in the code. To have our application work, we just have to force the compiler to take our class by adding the following variable definition in the MXML script:
    public var dummyBook:Book;

    This dummy definition will not be needed any more at the end when we will have a book form that references this class.



    You can check one of the important features of the Tide framework, the entity cache. If you select a row of the grid and click the refresh button, the selection is not lost, meaning that the instances of the books are exactly the same and are just updated with the data coming from the server.

    Ok, we now have a list, that would be useful to be able to create or edit books, so we need to add a few buttons.
    <?xml version="1.0" encoding="utf-8"?>

    <mx:Panel
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    width="100%"
    label="Book Manager"
    creationComplete="list()">

    <mx:Metadata>
    [Name]
    </mx:Metadata>

    <mx:Script>
    <![CDATA[
    import mx.events.ItemClickEvent;
    import mx.collections.ArrayCollection;
    import mx.controls.Alert;
    import mx.binding.utils.BindingUtils;
    import org.granite.tide.events.TideUIConversationEvent;
    import org.granite.tide.events.TideResultEvent;
    import org.granite.tide.spring.Spring;
    import org.granite.tide.spring.PagedQuery;

    public var dummyBook:Book;

    [Bindable] [In]
    public var bookListService:PagedQuery;

    [Observer("listBook")]
    public function list():void {
    bookListService.refresh();
    }

    private function initButtonBar():void {
    BindingUtils.bindProperty(bbMain.getChildAt(1), "enabled", dgList, "selectedItem");
    }

    private function clickHandler(event:ItemClickEvent):void {
    if (event.index == 0)
    dispatchEvent(new TideUIConversationEvent('New Book', 'editBook'));
    else if (event.index == 1 && dgList.selectedItem)
    dispatchEvent(new TideUIConversationEvent('Book#' + dgList.selectedItem.id, 'editBook', dgList.selectedItem));
    }
    ]]>
    </mx:Script>

    <mx:TabNavigator id="nav" width="100%" height="100%">
    <mx:VBox label="Book List">
    <mx:ButtonBar id="bbMain" creationComplete="initButtonBar()" itemClick="clickHandler(event);">
    <mx:dataProvider>
    <mx:Array>
    <mx:String>New Book</mx:String>
    <mx:String>Edit Book</mx:String>
    </mx:Array>
    </mx:dataProvider>
    </mx:ButtonBar>

    <mx:DataGrid id="dgList" dataProvider="{bookListService}" width="400" height="200">
    <mx:columns>
    <mx:DataGridColumn dataField="title"/>
    <mx:DataGridColumn dataField="author"/>
    </mx:columns>
    </mx:DataGrid>

    <mx:Button label="Refresh" click="list()"/>
    </mx:VBox>
    </mx:TabNavigator>

    </mx:Panel>

    Just adding buttons unfortunately won't be enough, we have to define a controller and UI for the editor panel. Note that the buttons we have defined just dispatch TideUIConversation events. Our application still works, but the new buttons just don't do anything as there is no observer for these events. This shows how the Observer pattern in the Tide framework can help decouple the various parts of the application. We'll see below what conversations mean.
    Before that, we have to build the Grails controller to edit books.
    grails create-controller BookEdit

    grails-app/controllers/BookEditController.groovy :
    import org.granite.tide.annotations.TideEnabled

    @TideEnabled
    class BookEditController {

    // The bookInstance property of the controller will be the model that we bind to the Flex view.
    Book bookInstance

    // The remove method has nothing very particular and is very similar
    // to the default delete method of the standard Grails controller scaffolding.
    def remove = {
    bookInstance = Book.get(params.id)
    if (bookInstance)
    bookInstance.delete()
    }

    // The merge and persist method are similar: we get the bookInstance from the params map
    // and merge/persist it in the current persistence context.
    // Note that we get directly a typed Hibernate detached entity from the params map,
    // we'll see below how this map is sent by the client.
    // The validation part is a little different from classic Grails controllers, so we'll see it later.
    def merge = {
    bookInstance = params.bookInstance

    if (!bookInstance.validate())
    throw new org.granite.tide.spring.SpringValidationException(bookInstance.errors);

    bookInstance = bookInstance.merge()
    }

    def persist = {
    bookInstance = params.bookInstance

    if (!bookInstance.validate())
    throw new org.granite.tide.spring.SpringValidationException(bookInstance.errors);

    bookInstance.save()
    }
    }

    And we keep the more complex part for the end.

    grails-app/views/BookEdit.mxml:
    <?xml version="1.0" encoding="utf-8"?>

    <mx:VBox
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    width="100%"
    label="{create ? "New book" : bookInstance.title}">

    <mx:Script>
    <![CDATA[
    import mx.controls.Alert;
    import mx.events.CloseEvent;
    import mx.containers.TabNavigator;
    import mx.collections.ArrayCollection;
    import org.granite.tide.spring.Context;
    import org.granite.tide.events.TideResultEvent;
    import org.granite.tide.events.TideFaultEvent;


    // The Tide context is injected in the component.
    // It will be used to manage the conversation.
    [Bindable] [In]
    public var context:Context;

    // We get injected with the main tab navigator with a kind of EL expression.
    // We could also have accessed the component with context.bookUI.nav
    [Bindable] [In("#{bookUI.nav}")]
    public var nav:TabNavigator;

    // We get injected with a proxy of the Grails edit controller.
    [Bindable] [In]
    public var bookEditController:Object;

    // We make a two-way binding between the bookInstance to the context.
    // So we can set the instance in this component and get it back from
    // outside.
    [Bindable] [In] [Out]
    public var bookInstance:Book;

    [Bindable]
    private var create:Boolean;


    // This observer will be called when someone dispatches the editBook Tide event.
    [Observer("editBook")]
    public function edit(bookInstance:Book = null):void {
    create = bookInstance == null;
    if (create) {
    this.bookInstance = new Book();
    this.bookInstance.chapters = new ArrayCollection();
    }
    else
    this.bookInstance = bookInstance;

    // Add itself as a child tab in the main view
    if (!nav.getChildByName(this.name))
    nav.addChild(this);
    nav.selectedChild = this;
    }


    private function save():void {
    bookInstance.title = title.text;
    bookInstance.author = author.text;
    bookInstance.description = description.text;

    // The 'Save' button calls the remote controller and passes it a params map.
    // That's how the Grails controller can get the book with params.bookInstance.
    if (create)
    bookEditController.persist({ bookInstance: bookInstance }, saveResult);
    else
    bookEditController.merge({ bookInstance: bookInstance }, saveResult);
    }

    // Result handler for the remote controller call
    private function saveResult(event:TideResultEvent):void {
    nav.removeChild(this);
    // If the book was a new one, trigger a refresh of the list to show the new book.
    // Note once again that we don't know here who will handle the raised event.
    if (create)
    context.raiseEvent("listBook");
    // Merges the current conversation context in the global context
    // (update the existing entities) and destroys the current conversation context.
    context.meta_end(true);
    }

    private function cancel():void {
    nav.removeChild(this);
    // The cancel button does not merge the conversation in the global context,
    // so all that has been modified in the form is discarded.
    context.meta_end(false);
    }


    public function remove():void {
    Alert.show('Are you sure ?', 'Confirmation', (Alert.YES | Alert.NO),
    null, removeConfirm);
    }

    private function removeConfirm(event:CloseEvent):void {
    if (event.detail == Alert.YES)
    bookEditController.remove({id: bookInstance.id}, removeResult);
    }

    private function removeResult(event:TideResultEvent):void {
    nav.removeChild(this);
    context.raiseEvent("listBook");
    context.meta_end(true);
    }
    ]]>
    </mx:Script>

    <mx:Panel>
    <mx:Form id="bookForm">
    <mx:FormItem label="Title">
    <mx:TextInput id="title" text="{bookInstance.title}"/>
    </mx:FormItem>
    <mx:FormItem label="Author">
    <mx:TextInput id="author" text="{bookInstance.author}"/>
    </mx:FormItem>
    <mx:FormItem label="Description">
    <mx:TextArea id="description" text="{bookInstance.description}" width="400" height="50"/>
    </mx:FormItem>
    </mx:Form>

    <mx:HBox>
    <mx:Button label="Save" click="save()"/>
    <mx:Button label="Delete" enabled="{!create}" click="remove()"/>
    <mx:Button label="Cancel" click="cancel()"/>
    </mx:HBox>
    </mx:Panel>

    </mx:VBox>

    The last thing is to define this MXML as a Tide-managed conversation component by adding this in a static block in BookUI.mxml.
    Spring.getInstance().addComponent("bookEdit", BookEdit, true);

    The last parameter set to true indicates that the component will be created in a conversation context. Conversations are a means to have separate component containers that have their own namespace. Each context also has its own entity cache, so changes on entities in a conversation are separate from other conversations.

    If you now browse http://localhost:8080/gdsexample/GDSExample.swf, you can check that you can create new books, but if you try to edit an existing book, you encounter the famous LazyInitializationException when clicking 'Save'. Remembering that the Grails controller gets detached objects, and that the chapters collection is defined lazy, it's clear that we cannot apply the default Grails validation to the bookInstance. We have to disable 'deepValidate' for now.

    grails-app/controllers/BookEditController.groovy:
    def merge = {
    bookInstance = params.bookInstance

    if (!bookInstance.validate([deepValidate: false]))
    throw new org.granite.tide.spring.SpringValidationException(bookInstance.errors);

    bookInstance = bookInstance.merge([deepValidate: false])
    }




    This time everything should work as expected, you can create, update and delete books. However we have left the chapters aside for now, except that it has brought an issue with lazy loading. We would still like to edit the chapters of a book in the form. All we have to do is to add an editable DataGrid in the Book form:
                <mx:FormItem label="Chapters">
    <mx:HBox>
    <mx:DataGrid id="chapters" dataProvider="{bookInstance.chapters}" editable="true">
    <mx:columns>
    <mx:DataGridColumn dataField="title"/>
    <mx:DataGridColumn dataField="summary"/>
    </mx:columns>
    </mx:DataGrid>

    <mx:VBox>
    <mx:Button label="Add"
    click="addChapter()"/>
    <mx:Button label="Remove"
    enabled="{Boolean(chapters.selectedItem)}"
    click="removeChapter()"/>
    </mx:VBox>
    </mx:HBox>
    </mx:FormItem>

    And the corresponding add/remove event handlers:
                private function addChapter():void {
    var chapter:Chapter = new Chapter();
    chapter.book = bookInstance;
    chapter.title = "";
    chapter.summary = "";
    bookInstance.chapters.addItem(chapter);
    chapters.editedItemPosition = {columnIndex: 0, rowIndex: bookInstance.chapters.length-1 };
    }

    private function removeChapter():void {
    bookInstance.chapters.removeItemAt(chapters.selectedIndex);
    }

    Now that the chapters collection is always initialized, we could even reactivate the deepValidate option in the BookEditController merge closure.



    Finally we will add a minimal validation to show the integration with Grails validation. First we have to activate the client handling of the validation errors in a static block of the main application:

    grails-app/views/GDSExample.mxml :
        <mx:Script>
    <![CDATA[
    import org.granite.tide.spring.Spring;
    import org.granite.tide.validators.ValidatorExceptionHandler;

    Spring.getInstance().addExceptionHandler(ValidatorExceptionHandler);
    ]]>
    </mx:Script>

    grails-app/views/BookEdit.mxml :
    <mx:VBox
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:tv="org.granite.tide.validators.*"
    ...

    <tv:TideEntityValidator entity="{bookInstance}" property="title" listener="{title}"/>
    </mx:VBox>




    We've just added a validator for the 'title' property, but it's easy to add one for every field. Of course that doesn't make much sense in this case because a purely client-side Flex validator would be more effective, but it can be used anywhere a Grails validation can.

    With this simple example, we have already seen many important features of GraniteDS/Tide: the Tide client container with injection and observers, the entity cache, the conversation contexts, lazy loading, data paging and Java server integration. The complete project files can be downloaded here and can just be unzipped in the Grails project folder after create-app and install-plugin.

    In a next post, I will show how you can easily add security to the mix, and maybe collaborative data updates between users with the 0.3 plugin.

    As always, feedback and suggestions are welcome.