Franck has posted 18 posts at DZone. View Full User Profile

Migrating Java EE Web Applications to Adobe Flex and Granite Data Services

01.09.2009
| 18513 views |
  • submit to reddit

One of the original goals of the Granite Data Services project (current version is 1.2.0 GA) was to make easier the migration of existing J2EE applications to Adobe Flex RIA technology. The reference framework used in our applications was rather classical: JSF + EJB + Hibernate.

The easy way was obviously to keep as is the service layer (EJB + Hibernate), working only on the Web layer (JSF) implementation. With Flex server technologies provided by Adobe (LCDS and BlazeDS), it is unlikely that you can keep your service layer as is: LCDS promotes is own concepts of service and persistence, while BlazeDS does not support one the most important feature of JPA engines: lazy-loading.

With GraniteDS, you get full JPA support and you can easily integrate not only with EJB but also with Seam, Spring and even Guice services. Basically, most of the work consists in migrating your JSF managed beans (or Struts beans) and views to the Flex client and GraniteDS will help you a lot with code generation tools and the Tide client framework.

For example, suppose that you have a simple data model like this one:

@Entity
public class Brand {

@Id @GeneratedValue
private int id;

@Basic
private String name;

(1) @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="brand")
private Set<Car> contacts = new HashSet<Car>();

// getters/setters...
}

@Entity
public class Car {

@Id @GeneratedValue
private int id;

@ManyToOne(optional=false)
private Brand brand;

@Basic
private String model;

// getters/setters...
}

(1) Note the FetchType.LAZY parameter on the cars collection.


Migrating your Web layer to Flex requires that you have an ActionScript3 representation of your entity beans. With the help of the Gas3 code generator (available as an Ant task or an Eclipse builder), this data model is automatically replicated in ActionScript3: by running the generator you'll get a Brand.as and a Car.as classes, whose implementation includes all necessary code required for data serialization from your Java services to your Flex client application and vice-versa (and the necessary lazy loading support implementation).

That's it for your data model, with no limitation in terms of complexity (inheritance, mapped superclass, embedded beans, inner enum declarations, etc.) You just use the code generator in order to mirror your Java model in its ActionScript3 equivalent.

Let's now say you have an EJB session bean with a basic find operation like this one:

@Session
public class CarServiceBean implements CarService {

@PersistenceContext
protected EntityManager manager;

public findAllBrands() {
return manager.createQuery("select b from Brand b").getResultList();
}
}

With Tide, to access this EJB from within your Flex application, you'll need a Flex services-config.xml configuration file like this one:

<services-config>
<services>
<service id="granite-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">
(1) <destination id="ejb">
<channels>
<channel ref="my-graniteamf"/>
</channels>
<properties>
(2) <factory>tideEjbFactory</factory>
(3) <entityManagerFactoryJndiName>java:/DefaultEMF</entityManagerFactoryJndiName>
</properties>
</destination>
</service>
</services>

<factories>
<factory id="tideEjbFactory" class="org.granite.tide.ejb.EjbServiceFactory">
<properties>
<lookup>myApp/{capitalized.component.name}Bean/local</lookup>
</properties>
</factory>
</factories>

<!-- skipped channel definition -->
</services-config>

The important parts in this config are:

(1) Only one destination is used and must be named "ejb" (this destination id will be automatically recognized and used by Tide for EJB without any further configuration).

(2) The destination is bound to the tideEjbFactory and this factory will manage to lookup the session bean based on a String pattern where {capitalized.component.name} will be replaced at runtime by the name of the service your are calling in your client code (see MXML code below).

(3) Because Tide must be able to access an EntityManager for transparent lazy-loading operations, you must also provide a valid JNDI name for retrieving an EntityManagerFactory instance.


Finally, we may write a very basic MXML application like this one:

<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
(1) creationComplete="creationComplete()">

<mx:Script>
<![CDATA[
import mx.collections.ListCollectionView;

import org.granite.tide.ejb.Ejb;
import org.granite.tide.ejb.Context;
import org.granite.tide.events.TideResultEvent;

[Bindable]
private var brands:ListCollectionView;

(2) private var tideContext:Context = Ejb.getInstance().getEjbContext();

(3) function creationComplete():void {
tideContext.carService.findAllBrands(findResult);
}

(4) function findResult(event:TideResultEvent):void {
brands = event.result;
}
]]>
</mx:Script>

<mx:HBox width="100%" height="100%">
(5) <mx:DataGrid id="dgBrands" dataProvider="{brands}"/>

(6) <mx:DataGrid dataProvider="{dgBrands.selectedItem.cars}"/>
</mx:HBox>

</mx:Application>

(1) When the Flex application is fully initialized, it calls a function named "creationComplete" (see (3)).

(2) Before the above call, the private variable tideContext is assigned with a singleton Context that allows services calls on the "ejb" destination.

(3) Then, the creationComplete function is executed and calls the findAllBrands method on our above CarServiceBean session EJB. The additional parameter is the name of a handler function called when the result will be available (all server calls are asynchronous).

(4) When the result is available, the "brands" variable is assigned with the collection of all brands available in the database. This collection contains instances of the previously generated Brand.as class that replicate corresponding Java entity beans loaded by the EntityManager.

(5) The first DataGrid ("dgBrands") is populated with Brand objets, whose cars collection is uninitialized (remember the the FetchType.LAZY parameter in the Brand Java class).

(6) Whenever you click on a brand in the first DataGrid, the selectedItem property is assigned with the selected Brand. Because the cars collection is accessed in order to populate the second DataGrid, this uninitialized collection is transparently loaded by Tide by issuing a server call with the selected brand: with the help of the EntityManager, the unitialized cars collection is fully loaded and returned to the Tide client context. Then, the second DataGrid is populated with all cars registered for the selected brand.

This quick overview of the GraniteDS framework doesn't give you the big picture of all GraniteDS capabilities: security services for Tomcat, Jetty, GlassFish and (soon) WebLogic servers (with specific implementations for the Spring and Seam frameworks), support of Hibernate, TopLink, EclipseLink and (soon) OpenJPA persistence engines, tight integration with the Seam framework (conversation and bijection support), data paging, injection and outjection in your Tide beans on Flex side (with specific ActionScript3 annotations), etc.

You may see more on Granite Data Services 1.2.0 GA release in GDS forum here.

Comments are welcome!

Published at DZone with permission of its author, Franck Wolff.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Craig Wickesser replied on Thu, 2009/01/15 - 8:10am

Looks promising.  How about support for RTMP (streaming) data to the client?  For example, my Flex client wants to listen to a JMS topic, can GDS support that, if so can it handle thousands of messages per second?

 

Franck Wolff replied on Fri, 2009/01/16 - 6:54am

GraniteDS does not implement the RTMP stack (the specification isn't public) but it provides true Comet support, based on Tomcat/JBossWeb CometProcessor or Jetty Continuation. The implementation rougly follows the Bayeux protocol (long polling only) and provides JMS integration (see documentation here).

About your second question: I didn't conduct large scale tests but, with a Tomcat installed on a simple laptop, I have been able to receive about 120 messages (12 clients sending 10 short String messages per second) and dispatch about 1440 (12*120) messages per second (each client receiving its own messages and all messages sent by other clients). I didn't try with more clients/more messages but I guess it would work as well (at least with a real server). Basically, the scalability of this feature mostly relies on the underlying implementation of the asynchronous http support (Tomcat/JBossWeb/Jetty) and benchmarks made by these servlet container providers should be relevant here.

haihong hu replied on Mon, 2009/03/30 - 3:40am

FetchType.LAZY didn't work

it can't load the cars with LAZY

WHY?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.