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.

mercredi 18 mars 2009

Migration notes for GraniteDS 2.0 beta 1

Hi,

GraniteDS 2.0 beta 1 has just been released. It contains a lot of changes and there are a few simple but important migration tips that must be followed in your projects to use this new version.

Packaging and deployment
The most visible change is the new packaging of the distribution. There is only one zip containing all jars/swcs in the build directory and an examples folder with all example projects that can be imported in an Eclipse workspace.
Many swcs have been removed thanks to a huge refactoring in the persistence code and there are only 2 swcs left:
- granite-essentials.swc is the core library and must be linked with -include-library
- granite.swc is the framework library and can be linked with normal Flex library linking (merge into code in Flex Builder) to reduce the final swf size

The Tide specific jars have been merged with the non-Tide jars, so server deployment now requires 2 or 3 jars depending only on the server framework and persistence provider used:
- granite.jar (the core library)
- one persistence jar (granite-hibernate.jar, granite-toplink.jar, granite-eclipselink.jar, granite-openjpa.jar, granite-datanucleus.jar)
- one server framework.jar (none, granite-spring.jar, granite-seam.jar, granite-guice.jar)

The next thing is to regenerate all your entities with the latest gas3 templates, with either the 2.0.0b1 builder or the 2.0.0b1 ant task. To check if your entities are up-to-date, just check that __laziness has been replaced by __detachedState and __initialized.

Other migration tips depend on the parts of GDS that you use:

Spring:
- The Spring service factory is now in the package org.granite.spring instead of org.granite.messaging.service. The change has to be done in services-config.xml.

Tide:
- Enable the following annotations:
Version,Name,In,Out,Observer,Destroy
Optionnally you can enable the Event annotation to try the new Tide event model.

- events dispatched upon user login/logout are org.granite.tide.login and org.granite.tide.logout instead of login/logout to avoid name conflict and handlers now get TideContextEvent instead of TideLoginEvent. TideLoginEvent has been removed.

- identity.forceLogout has been removed (identity.logout has been fixed with Flex JIRA BLZ-310 workaround)

Tide/Seam:
- JPA entities that will be managed by Tide must now be annotated with @EntityListener(org.granite.tide.data.DataListener.class). This can be done only once in an abstract base class or alternatively in the orm.xml configuration file.

- status messages are now available in context.statusMessages.messages instead of context.messages, and statusMessages can be injected as a client component with [In] public var statusMessages:StatusMessages.


A next post will describe the new features in this release in more details.

William

vendredi 9 janvier 2009

New features in GraniteDS 1.2 : Tide support for Seam 2.1 authorizations

Seam 2.1 is the first release that is not tied to the JSF view technology. The Tide integration benefits from this with a little easier deployment (no more JSF jars and servlet configuration).

The migration to Seam 2.1 is very easy : replace granite-tide-seam.jar by granite-tide-seam21.jar and change the security service to
<security type="org.granite.seam21.security.Seam21SecurityService"/>.

Not saying that you also have to change the Seam jars...

Another important new feature of Seam 2.1 is the powerful security/authorization mechanism. We have updated the Seam security service to use the new APIs, but we have mainly added Flex support for server authorizations. The Tide identity component now provides the methods hasRole and hasPermission that request access rights from the server.

The most simple use of these methods is to conditionally enable/show parts of the UI in mxml components depending on user authorizations :

<mx:DataGrid id="dg" dataProvider="{products}"/>

<mx:Button id="bUpdate" label="Update"
enabled="{dg.selectedItem != null}"
visible="{identity.hasPermission(dg.selectedItem, 'update')}"
click="updateProduct()"/>
<mx:Button id="bDelete" label="Delete"
enabled="{dg.selectedItem != null}"
visible="{identity.hasRole('admin')}"
click="deleteProduct()"/>


Here the 'update' button will be showed only if the user has the rights to update the selected entity and the 'delete' button will be shown only to administrators.

The important thing to note is that these access rights are cached on the client and are not requested from the server at each call. This greatly reduces the performance penalty of using them. The hasPermission method is also integrated with the entity cache, meaning that the permission cache for entities is cleared whenever an entity is cleared from the cache.

It is possible to manually clear the cache with identity.clearSecurityCache(). For example you could setup a timer and call this periodically.

The identity.hasRole and identity.hasPermission methods can also be used programmatically in a controller :
Identity.hasRole and Identity.hasPermission can also be used programmatically. As the value may be retrieved asynchonously, the return value of these methods may not be accurate if it was not already in the cache (they return false by default). The correct way of handling this is to pass a result handler function:

public function canDelete(product:Product):void {
identity.hasRole('admin', canDeleteResult);
}

private function canDeleteResult(event:TideResultEvent, role:String):void {
if (!event.result)
Alert.show("You cannot delete the product");
}

public function canUpdate(product:Product):void {
identity.hasPermission(product, 'update', canUpdateResult);
}

private function canUpdateResult(event:TideResultEvent, product:Product, action:String):void {
if (!event.result)
Alert.show("You cannot update the product " + product.name);
}


As getting the result can necessitate a remote call, the hasPermission and hasRole take a result handler that is called when the result is available (immediately when the result is cached). Beware that the direct return value of these methods may not be correct when the data is not in cache.

The above example shows that the result handlers for hasRole and hasPermission have additional arguments that allow to know about the requested role or permission when many permissions are checked simultaneously.

vendredi 26 décembre 2008

What's new in GraniteDS 1.2

GDS 1.2 brings a lot a bug fixes and a consequent number of new features.

1. The Gravity implementation for Tomcat with APR/NIO has been completely rewritten since 1.1 and is now a lot more stable. Additionally there is now Gravity support for JBossWeb, which is a modified Tomcat included in JBoss 5.0.0 GA.

2. We are starting to integrate new JPA providers and application servers. GDS 1.2 comes with a specific integration for TopLink (used in GlassFish V2, Sun AS and Oracle AS) and EclipseLink (RI for JPA 2.0 and used in GlassFish V3). It also brings a specific security service for GlassFish (really only a slightly modified Tomcat service). GDS 1.3 will bring additional support for WebLogic (security service and Kodo/OpenJPA) and probably other major open source JPA providers (JPOX/DataNucleus). Most likely we won't provide new specific Gravity integrations until there are more information and details on the timeline for the servlet 3 specification. For users wanting to use Gravity without the thread blocking issues, it seems possible though to benefit from the Jetty continuations mechanism in any application server by just putting the jetty-util.jar in WEB-INF/lib and setting the Gravity Jetty servlet.

3. Tide for Seam now fully supports Seam 2.1 and the two example projects now run with Seam 2.1.0.SP1. That allows to remove the dependency on JSF and brings a cleaner deployment. It is also fully integrated with Seam 2.1 authorizations and it's now possible to use role-based and permission-based security on the Flex client to make some components conditionally visible or to check for access rights before trying to issue service calls. You can check the graniteds-seam project for example of such use.

4. Tide for Spring and Tide for EJB have been improved and should be fully functional on all supported combinations of application servers/data access providers. In particular Tide/Spring now works correctly with plain Hibernate, without JPA and without JTA and can use the Session API (but still requires the use of annotations). Both integrations provide zero-conf remoting, entity caching, transparent lazy loading of collections and data paging. They can also be completely integrated with the Tide client framework.

5. It is now possible to use a client-only version of the Tide client framework with any server backend (AMFPHP, RubyAMF...). In this case, available data features will be limited to simplified remoting and client entity caching. Data paging is not available out of the box but can be used with minor developments.

There are other various improvements and features. The following blog posts will describe these new features in more details.

mardi 2 décembre 2008

GraniteDS at Devoxx 2008

Hi all,

I'll give a short presentation of GraniteDS at Devoxx 2008 (Antwerp/Belgium) on Dec. 11, 14:00-15:00.
William will be around and we both expect to meet nice and interesting people.

Come and join us!

Regards,
Franck.

jeudi 20 novembre 2008

Support for Seam 2.1

Support for Seam 2.1 is now in available the trunk and in the latest
GDS nightly builds
(http://www.graniteds.org/bamboo/browse/GDS-SHOTS/latest). Don't
hesitate to give it a try if you are interested to help us testing it
before the stable release.

As a few parts of the API have changed, there are some little
differences of configuration and deployment.

First you will need to use the granite-tide-seam21.jar instead of
granite-tide-seam.jar. The swc library is the same granite-tide-seam.swc.

Most implementation classes are different :

<granite-config scan="true">
<!--
! Use Seam 2.1 based security service.
!-->
<security type="org.granite.seam21.security.Seam21SecurityService"/>

<!--
! Enable Seam components for Tide
!-->
<tide-components>
<component instanceof="org.jboss.seam.security.Identity"/>
<component instanceof="org.jboss.seam.framework.Home"/>
<component instanceof="org.jboss.seam.framework.Query"/>
<component annotatedwith="org.granite.tide.annotations.TideEnabled"/>
</tide-components>
</granite-config>

services-config.xml is exactly the same.

The good news is that you can completely remove all dependencies on
Faces if you don't use it. Thus web.xml can be as simple as :

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<display-name>GraniteDS Seam 2.1</display-name>

<!-- Seam -->

<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>

<filter>
<filter-name>AMFMessageFilter</filter-name>
<filter-class>org.granite.messaging.webapp.AMFMessageFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>AMFMessageFilter</filter-name>
<url-pattern>/graniteamf/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>AMFMessageServlet</servlet-name>
<servlet-class>org.granite.messaging.webapp.AMFMessageServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>AMFMessageServlet</servlet-name>
<url-pattern>/graniteamf/*</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>


More complete details are here :
http://www.graniteds.org/confluence/display/DOC/3.+Configuration

Remember that if you remove Faces, you will not be able to use
FacesMessages any more (of course) and you will have to use the new
Seam 2.1 StatusMessages API. By default, Tide comes with a basic
implementation of it but it's not necessary to refer explicitly to the
concrete Tide implementation class and you can just use
@In StatusMessages statusMessage;

If you need to use a html view layer side by side with Flex/Tide, this
default TideStatusMessages will be overriden by the view-specific
implementation and you won't have to change anything in the components.

mercredi 19 novembre 2008

GraniteDS Tide for Spring

Tide for JBoss Seam has been available for a while now. The integration is as close as possible mainly because the architecture of the Tide client-side library is heavily inspired by the Seam architecture on the server : stateful components, bijection, conversations...

Spring is different as it promotes a more stateless server architecture and is overall more focused on providing sophisticated low level abilities. Moreover we at graniteds.org have far less experience with it than with JBoss technologies (except Bouiaw who contributed most of the server-side integration).

Consequently the current Tide integration with Spring is maybe not as complete as it could but is already working and provides the most important features :
- Simplified zero configuration remoting
- Managed entities caching on the client
- Data paging supporting remote sorting and filtering
- Transparent loading of lazy collections
- Integration with Hibernate validator
- Tide Flex 3 client framework

Here is a short presentation of the current state of this integration. Most things will work exactly as the Seam version, with very minor differences. Note that most of these features are already present in the GDS 1.1.0 version but will be more polished in the upcoming minor release, with corresponding documentation.

First the inevitable Hello World :

Spring bean (2.5 style with annotations) :

@Service("helloBean")
public class HelloBeanImpl implements HelloBean {
public String sayHello(String name) {
return "Hello, " + name;
}
}


Flex code :

import org.granite.tide.spring.Spring;
import org.granite.tide.spring.Context;

public function init():void {
// The main piece : getting the client context
var tideContext:Context = Spring.getInstance().getSpringContext();
// Triggers the remote call of the 'helloAction' component
tideContext.helloBean.sayHello("Jimi", resultHandler);
}

private function resultHandler(event:TideResultEvent):void {
// Should output 'Hello, Jimi'...
trace(event.result);
}


It's hard to find a difference with the Seam version, for the good reason that there is none, except the name of Tide static factory object (Spring) and of the various packages.

In fact the only client-side impact of using Spring on the server is that you cannot use injection/outjection to transmit data to and from the server beans.
All remote calls will have to be stateless. This seems in line with the Spring application style.


Entity caching

A particular case is when managed entities are transmitted as arguments or return objects of remote calls through Tide.

Spring bean (2.5 style with annotations) :

@Service("personBean")
public class PersonBeanImpl implements PersonBean {

@PersistenceContext
private EntityManager manager;

public Person createPerson(Person person) {
manager.persist(person);
manager.flush();
return person;
}
}


Flex code :

import org.granite.tide.spring.Spring;
import org.granite.tide.spring.Context;

[Bindable]
public var person:Person;

public function createPerson():void {
person = new Person();
person.firstName = "Jimi";
person.lastName = "Hendrix";
tideContext.personBean.createPerson(person, createPersonResult);
}

private function createPersonResult(event:TideResultEvent):void {
person = event.result as Person;
}


There seems to be nothing very special here, except that Tide has merged the content of the original entity with the data from the Person object received from the server. Only the modified properties will then trigger PropertyChangeEvents, not the complete person variable. This is important if you have bound UI components to this particular Person instance.
In fact the whole result handler could even be omitted in this case, because Tide has already modified the person variable before the assignment. There will always be only one instance of an entity with a particular uid in a Tide context (the entity uid is very important for this reason, see corresponding section 'Managed entities' in the docs).


The other particular case is the one of collections :

@Service("personsBean")
public class PersonsBeanImpl implements PersonsBean {

@PersistenceContext
private EntityManager manager;

public List findAllPersons() {
Query q = manager.createQuery("select p from Person p");
return q.list();
}
}


Flex code :

import org.granite.tide.spring.Spring;
import org.granite.tide.spring.Context;

[Bindable] [Out]
public var persons:ArrayCollection;

public function findPersons():void {
person = new Person();
person.firstName = "Jimi";
person.lastName = "Hendrix";
tideContext.personsBean.findAllPersons(findAllPersonsResult);
}

private function findAllPersonsResult(event:TideResultEvent):void {
persons = event.result as ArrayCollection;
// Could have been replaced by tideContext.persons = event.result as ArrayCollection if client framework ([Out] annotation) is not used.
}


Here it is important to assign the result of the remote call to a context variable (either directly or through outjection) during the result handler method if you want to maintain the same collection variable instance between multiple calls to 'findPersons' (very handy when using data binding in DataGrids and only way to correcly use Flex data effects).

The variable assignment will in fact not replace the variable itself but instead merge the received collection content with the content of the existing variable instance. This is (a little) more involving than for entities because there is no uid here that can be used to match the received collection with the existing one (with Seam oujection we could use the name of the context variable).
Of course if you don't make this assignment you will get the correct results but the collection instance will be different (and data bindings will likely be broken also).


Data paging

The good news here is that there is a PagedQuery component in the Tide Spring client library. The bad news is that Spring does not provide an equivalent of the Seam Query component, it will be necessary to build one for each query. It is certainly possible to provide a generic Query component for Spring but this is definitely not in the scope of GDS and it seems that this feature has already been requested by people on the Spring framework.

For now it will be necessary to implement a Spring bean having a method with the following signature :

public Map find(Map filter, int first, int max, String order, boolean desc);


first, max, order and desc are straightforward.
filter is a map containing the parameter values of the query. These values can be set on the client by tideContext.queryBean.filter.parameter1 = value1;
The return object must be a map containing 4 properties :
- firstResult : should be exactly the same as the argument passed in
- maxResults : should be exactly the same as the argument passed in, except when its value is 0, meaning that the client component is initializing and needs a max value. In this case, you have to set the page value (must absolutely be greater than the maximum expected number of elements displayed simultaneously in a table).
- resultCount : number of results
- resultList : list of results

The following code snippet is a quick and dirty implementation :

@Service("people")
@Transactional(readOnly=true)
public class PeopleServiceImpl implements PeopleService {

@PersistenceContext(type = PersistenceContextType.EXTENDED)
protected EntityManager manager;


public Map find(Map filter, int first, int max, String order, boolean desc) {
Map result = new HashMap(4);
String from = "from " + Person.class.getName() + " e ";
String where = "where lower(e.lastName) like '%' || lower(:lastName) || '%' ";
String orderBy = order != null ? "order by e." + order + (desc ? " desc" : "") : "";
String lastName = filter.containsKey("lastName") ? (String)filter.get("lastName") : "";
Query qc = manager.createQuery("select count(e) " + from + where);
qc.setParameter("lastName", lastName);
long resultCount = (Long)qc.getSingleResult();
if (max == 0)
max = 36;
Query ql = manager.createQuery("select e " + from + where + orderBy);
ql.setFirstResult(first);
ql.setMaxResults(max);
ql.setParameter("lastName", lastName);
List resultList = ql.getResultList();
result.put("firstResult", first);
result.put("maxResults", max);
result.put("resultCount", resultCount);
result.put("resultList", resultList);
return result;
}
}



Lazy loading of collections

As with Seam, there is almost nothing to do to have this working.
You will just have to specify the name of the EntityManagerFactory Spring bean that will be used to execute the loading as a property of the Tide destination.

<destination id="spring">
<channels>
<channel ref="my-graniteamf"/>
</channels>
<properties>
<factory>tideSpringFactory</factory>
<entityManagerFactoryBeanName>entityManagerFactory</entityManagerFactoryBeanName>
</properties>
</destination>


Alternatively, it's possible to use a non-JPA persistence manager by defining a persistenceManagerBeanName instead of the entityManagerFactoryBeanName. For example, a plain Hibernate persistence manager is available and can be configured with :
<destination id="spring">
<channels>
<channel ref="my-graniteamf"/>
</channels>
<properties>
<factory>tideSpringFactory</factory>
<persistenceManagerBeanName>tidePersistenceManager</persistenceManagerBeanName>
</properties>
</destination>


And in applicationContext.xml, add the persistence manager and define it as transactional read-only :
<bean id="tidePersistenceManager" class="org.granite.tide.hibernate.HibernateSessionManager"
scope="request">
<constructor-arg>
<ref bean="sessionFactory"/>
</constructor-arg>
</bean>

<aop:config>
<aop:pointcut id="tidePersistenceManagerMethods"
expression="execution(* org.granite.tide.ITidePersistenceManager.*(..))"/>
<aop:advisor advice-ref="tidePersistenceManagerMethodsTxAdvice"
pointcut-ref="tidePersistenceManagerMethods"/>
</aop:config>

<tx:advice id="tidePersistenceManagerMethodsTxAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>



Integration with Hibernate Validator

The Tide Flex validators should work with Spring and server validation is enabled by default if Hibernate Validator in found on the classpath.


Conclusion

We now have seen the whole thing.
There is nothing particularly impressive compared to the Seam integration, but this is a good step forward.
More than usually we need feedback from the courageous people who will try this to improve and extend the integration with features more specific to Spring.