vendredi 18 juillet 2008

New features of GDS/Tide in 1.1 RC3

The GDS 1.1 RC3 is an important release for the Tide project because it brings a host of new features, most of which have been requested by the Tide users, and which are very powerful and easy to use :

1. Configuration of Tide-enabled components
2. Transparent lazy loading of collections
3. Paged collections, based on the Seam Query component
4. Propagation of Seam events on the Flex client, both synchronous and asynchronous

This makes this release of Tide feature complete. The upcoming final 1.1 release will contain only bugfixes.


Configuration of Tide-enabled components :

The early Tide versions were quite permissive concerning component access.
It was possible to call any component, even system components or entityManager or anything else. This was obviously a security hole.

The RC3 mandates a new (small) configuration to enable only the needed components.
In granite-config.xml, there is a new section which looks like this :

<tide-components>
<component type="org\.myapp\..*"/>
<component name="org\.myapp\..*"/>
<component instanceof="org.jboss.seam.framework.Query"/>
<component annotatedwith="javax.ejb.Stateless"/>
</tide-components>

The 4 methods of enabling Seam components for Tide are :
- by class name match (type=".."), which enables all Seam components which qualified class name matches the regex
- by component name match (name=".."), which enables all Seam components which qualified name matches the regex
- by component class type (instanceof=".."), which enables all Seam components which class extends or implements the specified class/interface
- by component class annotation (annotatedwith=".."), which enables all Seam components which class is annotated with the specified class/interface

The last method can be used in conjuction with either the WebRemote annotation of Seam or with the Tide-specific org.granite.tide.annotations.TideEnabled annotation.

This is important to carefully expose your Seam components, otherwise your application can have a big security hole.
This new configuration is flexible enough and if correctly used, should not be too hard to maintain.


Transparent lazy loading :

This is a most requested feature, as lazy exceptions are already a pain in a pure web environment, and even more in a RIA.
Tide tries to provide a convenient approach to automate this task, particularly in the context of a Seam conversation.

All uninitialized collections retrieved from the server are wrapped by a particular collection implementation, which can be used as data provider for Flex UI component.
When requested by the UI, these collections will trigger a server call to retrieve initialized data. This process follows the standard Flex ItemPendingError mechanism and works with any component handling correctly this mechanism (i.e. DataGrid and List).

For example, if you have an Order object with a lazy collection of Items, you can do :

<mx:DataGrid id="orders" dataProvider="{tideContext.orders}" change="if (selectedItem) { orderItems.dataProvider=selectedItem.items }">
...
</mx:DataGrid>

<mx:DataGrid id="orderItems">
...
</mx:DataGrid>

The collection is fetched only if it is bound to a component, so if the bindings are correctly designed, this transparent loading should not degrade performance by issuing too many server calls.


Paged collection :

The Flex UI components are designed to be able to fetch data on demand, and wait for remote operation when needed, by the use of the ItemPendingError exception.
Unfortunately, there is no standard collection implementation in the Flex SDK which is able to benefit from this feature and provide paginated access to remote data.

The paginated data access is part of the enterprise features of Adobe LCDS, and even this comes with some limitations :
- remote data are retrieved by page, but never cleaned, so when iterating on a LCDS paged collection, you finish by having the whole data on the client
- remote sorting is not automatic, it necessitates to trigger manually a server call on DataGrid events
- there is no way of managing remote filtering

On the other side, Seam provides a very handy Query component, which can simplify many operations on queries, in particular sorting and filtering data.

The goal of the Tide Query integration is to provide a paged collection implementation which completely manages the interaction between the Flex client and the Seam Query component.
It provides the following features :
- it supports any Flex component correctly managing ItemPendingError exceptions (DataGrid and List do)
- the client caches at most 2 pages of data, that allows to manage any amount of remote data without filling the Flash player memory
- it manages automatically the remote sorting of data. There is absolutely nothing to do manually, the selected Sort on the client is automatically propagated to the order property of the Seam component.
- it manages automatically the remote filtering of data. When used in conjunction with the Tide client context, an example object can be tracked for changes and be sent to the Seam query to be used as restrictions for the query.

A simple example is probably more clear :

Seam components.xml :

<component name="examplePerson" class="test.granite.ejb3.entity.Person">

<framework:entity-query name="people"
ejbql="select p from Person p"
max-results="36">
<framework:restrictions>
<value>lower(p.lastName) like lower( #{examplePerson.lastName} || '%' )</value>
</framework:restrictions>
</framework:entity-query>

This gives us a very classic Seam Query component, the only important thing being the max-results property, which will serve as a the page size for the client collection. It is absolutely necessary that the page size is greater than the maximum number of elements expected to be displayed on the UI component (in general 30-50 is a good fit).

On the Flex side,

import org.granite.tide.seam.framework.PagedQuery;

var seamContext:Context = Seam.getInstance().getSeamContext();

function initColl():void {
Seam.getInstance().addComponent("people", PagedQuery);

people.dataProvider = seamContext.people;
}

<mx:TextInput id="search" text="{seamContext.examplePerson.lastName}"
enter="seamContext.examplePerson.lastName = search.text; seamContext.people.refresh();"/>
<mx:button label="Search" click="seamContext.examplePerson.lastName = search.text; seamContext.people.refresh();">
<mx:datagrid id="people">
<mx:column datafield="firstName" headertext="First Name"/>
<mx:column datafield="lastName" headertext="Last Name"/>
</mx:DataGrid>

This is basically all which is needed to bridge our Seam query component with the Flex UI.
Note that the PagedQuery class actually extends ListCollectionView, and thus can be used whereever a collection can be used in Flex.


Propagation of Seam events :


In this part, we will only show a simple propagation of synchronous events. The asynchronous processing necessitates a little more configuration as Gravity is involved and this will be the subject for the second part of this post.

A new method in the Context object allows the client to register listeners for server events it is interested in.
For example, you can call :
seamContext.addContextEventListener(type:String, listener:Function, remote:Boolean = false);

- type is the type of the Seam event,
- listener is a handler function, which a signature function listener(event:TideContextEvent):void
- remote indicates that the event is expected to come from the remote server

Once registered, the listener function will be called after any remote call during which the interesting event type is raised by a component.

Let's take the Seam booking example :

public class HotelBookingAction implements HotelBooking {

@In private Events events;
...

@End
public void confirm() {
...
events.raiseTransactionSuccessEvent("bookingConfirmed");
}
}

On the Flex side, we could do this :

private function init():void {
...
seamContext.addContextEventListener("bookingConfirmed", bookingConfirmedHandler, true);
...
}

private function confirmBooking():void {
seamContext.hotelBooking.confirm();
}

private function bookingConfirmedHandler(event:TideContextEvent):void {
Alert.show("Booking confirmed");
seamContext.bookingList.refresh();
}

The bookingConfirmedHandler callback should be called when the remote call to hotelBooking.confirm() returns, because we have registered interest in the event, and it has been raised by the Seam component.

Handling of asynchronous events is very similar but needs a little more configuration to integrate with Gravity push.
The integration is made at two levels :
- events raised asynchronously by Seam components of the user context
- events raised by asynchronous method calls from Seam components of the user context

The first thing is to configure a messaging destination named 'seamAsync' in services-config.xml which will handle communication with the server components :

<services-config>

<services>
<service id="granite-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">
<!--
! Use "tideSeamFactory" and "my-graniteamf" for "seam" destination (see below).
!-->
<destination id="seam">
<channels>
<channel ref="my-graniteamf"/>
</channels>
<properties>
<factory>tideSeamFactory</factory>
</properties>
<security>
<security-constraint>
<auth-method>Custom</auth-method>
<roles>
<role>user</role>
<role>admin</role>
</roles>
</security-constraint>
</security>
</destination>
</service>

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

<destination id="seamAsync">
<channels>
<channel ref="my-gravityamf"/>
</channels>
<security>
<security-constraint>
<auth-method>Custom</auth-method>
<roles>
<role>user</role>
<role>admin</role>
</roles>
</security-constraint>
</security>
<adapter ref="seam"/>
</destination>
</service>
</services>

<!--
! Declare Tide+Seam service factory.
!-->
<factories>
<factory id="tideSeamFactory" class="org.granite.tide.seam.SeamServiceFactory"/>
</factories>

<!--
! Declare granite channels.
!-->
<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}/gravity/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>

</services-config>


Second, we need to startup the messaging client at the beginning of the application, for example in a static initializer block of our main mxml :

{
Seam.getInstance().addPlugin(TideAsync.getInstance("seamAsync"));
}

That's all.


Now we are able to listen to events raised asynchronously with Events.instance().raiseAsynchronousEvent() or raiseTimedEvent(), or to events raised from an asynchronous method call.

The client part is exactly the same as for synchronous events :

tideContext.addContextEventListener("notification", notificationHandler, true);

For security reasons, only events initiated by a component in the current user session context can be received. This cannot be used for now to communicate between different users.

Possible uses of this feature could be :
- notify the user of incoming events (new mail, new chat message, ...)
- notify the user periodically of the progress of a long process running on the server

This is really powerful and makes the use of the Seam asynchronicity framework almost transparent for the Flex client.
The client code is exactly the same, so you can make any method call asynchronous or not depending on the needs of the application.


You can try and see most of these things working on the example projects (graniteds_seam and graniteds_seam_booking). You can also go to the GDS forum for any help or comments.

A similar integration for Spring is in progress and may be included in a future release (maybe 1.2).

mardi 8 avril 2008

Quick presentation of the GraniteDS Tide project

We are in the process of releasing the GraniteDS Tide subproject as part of GraniteDS 1.1.

This new subproject is aimed at improving the data services part of GraniteDS and simplify the Flex client programming model when integrating with server side frameworks.

In a first step Tide will be focused on a strong integration with Seam and Hibernate, built on the existing GraniteDS/Seam integration, to extend the Seam programming model to the Flex client and make the use of remote Seam components very easy.

The main goal of Tide is to remove all the useless and redundant declarations and integration code both on Flex side and on server side, for example the dozens of destinations for RemoteObjects in service-config.xml and mxmls, and the necessary service adapters on the server.

Tide is heavily based on the concept of a client context, which proxies all communications between the client and the server. This client context provides an entity cache and a component container, similar to the server PersistenceContext and Seam context.

HelloWorld with Tide

Seam component :

@Stateless
@Name("helloAction")
public class HelloAction implements HelloLocal {
public String sayHello(String name) {
return "Hello, " + name;
}
}


Flex code :

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

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


This is very similar to the JavaScript Seam remoting way of calling server components. The Context object proxies all calls on its managed components and transmits them to the server through a single hidden RemoteObject. That means that just one destination has to be declared in services-config.xml.
A faultHandler can optionally be specified for handling errors.


Using existing Seam components

Let's go one step further with an extract of the Seam contactlist example :

Seam components :

<fwk:entity-query name="contacts" results="5">
<fwk:ejbql>from Contact</fwk:ejbql>
<fwk:order>lastName</fwk:order>
<fwk:restrictions>
<value>lower(firstName) like lower(concat(#{exampleContact.firstName}, '%'))</value>
<value>lower(lastName) like lower(concat(#{exampleContact.lastName}, '%'))</value>
</fwk:restrictions>
</fwk:entity-query>

<component name="exampleContact" class="org.jboss.seam.example.contactlist.Contact"/>

Flex code :

public function init():void {
var tideContext:Context = Seam.getInstance().getSeamContext();
tideContext.exampleContact.firstName = '';
tideContext.exampleContact.lastName = 'hendrix';
// Triggers the remote call of the 'contacts' component.
tideContext.contacts.getResultList(resultHandler);
}

private function resultHandler(event:TideResultEvent):void {
var list:ArrayCollection = ArrayCollection(event.result);
}


Here the main point is that the exampleContact properties have been sent to the server along with the remote call of getResultList. As the Context is an ActionScript proxy, it is able to detect all assignments on objects attached to it and to propagate them when needed.

Another kind of use is also possible and makes binding of server objects in mxml very easy :

MXML code :

<mx:textinput id="inputFirstName"/>
<mx:textinput id="inputLastName"/>
<mx:button label="Go"
click="tideContext.exampleContact.firstName = inputFirstName.text;
tideContext.examplePerson.lastName = inputLastName.text;
tideContext.contacts.refresh();"/>

<mx:datagrid id="contacts" dataprovider="{tideContext.contacts.resultList}">
...
</mx:datagrid>


The resultList collection will be updated each time the refresh() method is called on the remote component. Interestingly, the collection instance itself is not replaced, instead data received from the server are merged with the existing data, and even the instances of Contact are kept and merged when possible, allowing the DataGrid to correctly keep its selectedItem (this would not be the case if the object instances were overwritten).


Integration with Seam bijection

Still better, Tide integrates with the Seam component bijection interceptor and all outjected objects and datamodels are propagated from the server to the client context.
Here is an example from Seam booking :

Seam component :

@Stateful
@Name("hotelSearch")
@Scope(ScopeType.SESSION)
@Restrict("#{identity.loggedIn}")
public class HotelSearchingAction implements HotelSearching {

@PersistenceContext private EntityManager em;

private String searchString;
...

@DataModel private List hotels;

public void find() {
page = 0;
queryHotels();
}

public void nextPage() {
page++;
queryHotels();
}
...

public String getSearchString() {
return searchString;
}

public void setSearchString(String searchString) {
this.searchString = searchString;
}
...
}

MXML code :

<mx:textinput id="inputSearchString"/>
<mx:button label="Search"
click="tideContext.hotelSearch.searchString = inputSearchString.text;
tideContext.hotelSearch.find();"/>

<mx:datagrid id="hotels" dataprovider="{tideContext.hotels}">
...
</mx:datagrid>



This 3 relatively simple examples are significantly less verbose and easier to read than the classic way with RemoteObjects.


Advanced features

But Tide does not stop here and comes with some more advanced features which allow a more complete integration with the server side :

Propagation of context messages from the server

All Seam context messages are serialized back to the client and available in the client context after each remote call.

Flex code :

public function login(username:String, password:String):void {
tideContext.identity.username = username;
tideContext.identity.password = password;
tideContext.identity.login(loginHandler);
}

private function loginHandler(event:TideResultEvent):void {
var message:FacesMessage = tideContext.messages.getItemAt(0);
}


Integration with Seam security, in particular the identity component

The Tide client identity component takes care of both the RemoteObject credentials and the server Seam security, allowing a clean login/logout functionality.


Support for client conversations with possible transparent synchronization with a server conversation.
A new separate context object is created for each new server conversation. All remote calls made from this new context are then considered to be in the same server conversation.

Seam component :

@Stateful
@Name("hotelBooking")
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking {
...
@Begin
public void selectHotel(Hotel selectedHotel) {
hotel = em.merge(selectedHotel);
}
...
}

Flex code :

private function selectHotel(hotel:Hotel):void {
tideContext.hotelBooking.selectHotel(hotel, selectHotelHandler);
}

private function selectHotelHandler(event:TideResultEvent):void {
// Get a new client context allowing interaction with the server conversation
var conversationContext:Context = event.context as Context;
}


Integration with server side validation (Hibernate Validator)

A TideValidator component listening for server validation errors can be attached to any Flex control and trigger the display of the error message.

MXML code :

<mx:textinput id="inputEmail" text="{tideContext.contactHome.instance.email}"/>

<tide:tidevalidator entity="{tideContext.contactHome.instance}"
property="email" listener="{inputEmail}"/>


Conclusion

The project is still alpha and can only be found in the nightly builds or built from the subversion trunk for the moment, but we expect to get a preview version in the forthcoming GraniteDS 1.1 RC and a stable version as part of the final release of GraniteDS 1.1.

Two sample projects are available :
- The graniteds_seam_booking project, using most features of Tide.
- The graniteds_seam simple address book.

We hope that it will greatly simplify the development for people currently working with both Flex and Seam.

Don't hesitate to try it and give some feedback.