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.
Granite Data Services is an open source (LGPL2) framework for Adobe Flex/JavaEE application developments.
vendredi 26 décembre 2008
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.
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 :
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 :
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.
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) :
Flex code :
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) :
Flex code :
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 :
Flex code :
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 :
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 :
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.
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 :
And in applicationContext.xml, add the persistence manager and define it as transactional read-only :
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.
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 ListfindAllPersons() {
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 Mapfind(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 Mapfind(Map filter, int first, int max, String order, boolean desc) {
Mapresult = 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.
mardi 21 octobre 2008
The GDS/Tide Flex 3 framework
Since 1.1 RC3, the Tide framework has been including a not much advertised Flex framework, that is tightly integrated with the whole GDS/Tide data management / service integration functionalities and can completely remove the need for using Cairngorm or PureMVC to develop properly structured Flex applications.
The Tide Flex framework is heavily inspired from JBoss Seam, with an architecture based on stateful components. It borrows the most important concepts of Seam: bijection, events, conversations and is based on Flex 3 custom annotations. In general it shares with Seam the goal to eliminate complexity, thus allows to use simple annotated ActionScript classes with very little configuration.
A Tide application is composed of 3 kinds of components :
- Managed entities (the data model)
- UI view (annotated mxml or ActionScript Flex components)
- Tide components (annotated ActionScript classes)
The overall architecture is centered around the Tide context, which serves as an event bus between the UI view, the Tide components and the server, and as a container for managed entities.
The Tide components contain the core logic of the application. They play the same role on the client as Seam components on the server. Compared to Cairngorm for example, they share similar functionalities with commands and business delegates.
In general, they will have the following activities :
Let's look at a very simple example of a search list to show how this works :
Seam component (simply extracted from the Seam booking sample) :
MXML application :
Tide component :
This simple example demonstrates much of the concepts of the Tide framework. Using the context as a central repository for all elements of the application allows a correct decoupling between the UI layer and the application logic, while keeping the amount of classes and code to a minimum. The framework contains a few more features, notably support for client conversations, but keeps relatively lightweight.
Combined with Seam on the server, it does most of the tedious integration work automatically and helps developers to focus on the real application logic.
The Tide sample projects in the GDS distribution do not fully use the framework because they must support Flex 2 compatibility.
Two Flex 3-only projects graniteds_seam_fx3 and graniteds_seam_booking_fx3 have been adapted to benefit from the Tide framework capabilties and are available in the GDS subversion repository at https://granite.svn.sourceforge.net/svnroot/granite.
A more complete description is available in the documentation here.
As always, feedback is more than welcome.
The Tide Flex framework is heavily inspired from JBoss Seam, with an architecture based on stateful components. It borrows the most important concepts of Seam: bijection, events, conversations and is based on Flex 3 custom annotations. In general it shares with Seam the goal to eliminate complexity, thus allows to use simple annotated ActionScript classes with very little configuration.
A Tide application is composed of 3 kinds of components :
- Managed entities (the data model)
- UI view (annotated mxml or ActionScript Flex components)
- Tide components (annotated ActionScript classes)
The overall architecture is centered around the Tide context, which serves as an event bus between the UI view, the Tide components and the server, and as a container for managed entities.
The Tide components contain the core logic of the application. They play the same role on the client as Seam components on the server. Compared to Cairngorm for example, they share similar functionalities with commands and business delegates.
In general, they will have the following activities :
- observe incoming events (coming from the UI or from the server)
- execute the business logic of the client application
- control the state of the UI
- interact with the server
Let's look at a very simple example of a search list to show how this works :
Seam component (simply extracted from the Seam booking sample) :
@Stateful
@Name("hotelSearch")
@Scope(ScopeType.SESSION)
@Restrict("#{identity.loggedIn}")
public class HotelSearchingAction implements HotelSearching {
@PersistenceContext
private EntityManager em;
private String searchString;
private int pageSize = 10;
private int page;
@DataModel
private Listhotels;
public void find() {
page = 0;
queryHotels();
}
...
private void queryHotels() {
hotels = em.createQuery("select h from Hotel h where lower(h.name) like #{pattern} or lower(h.city) like #{pattern} or lower(h.zip) like #{pattern} or lower(h.address) like #{pattern}")
.setMaxResults(pageSize)
.setFirstResult( page * pageSize )
.getResultList();
}
...
public ListgetHotels() {
return this.hotels;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
@Factory(value="pattern", scope=ScopeType.EVENT)
public String getSearchPattern() {
return searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';
}
public String getSearchString() {
return searchString;
}
public void setSearchString(String searchString) {
this.searchString = searchString;
}
@Remove
public void destroy() {}
}
MXML application :
<mx:Application creationComplete="init();">
<mx:Script>
[Bindable]
(1) private var tideContext:Context = Seam.getInstance().getSeamContext();
// Components initialization in a static block
{
(2) Seam.getInstance().addComponents([HotelsCtl]);
}
[Bindable] [In]
(3) public var hotels:ArrayCollection;
private function init():void {
(4) tideContext.mainAppUI = this;
}
private function search(searchString:String):void {
(5) dispatchEvent(new TideUIEvent("searchForHotels", searchString));
}
</mx:Script>
<mx:Panel>
<mx:TextInput id="fSearchString"/>
<mx:Button label="Search" click="search(fSearchString.text);/>
<mx:DataGrid id="dgHotels" dataProvider="{hotels}">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="name"/>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
</mx:Application>
Tide component :
import mx.collections.ArrayCollection;
[Name("hotelsCtl")]
[Bindable]
public class HotelsCtl {
[In]
(6) public var hotels:ArrayCollection;
[In]
(7) public var hotelSearch:Object;
(8) [Observer("searchForHotels")]
public function search(searchString:String):void {
(9) hotelSearch.searchString = text;
hotelSearch.find();
}
}
- The main application initializes and gets the Tide context.
- The application registers the component class HotelsCtl in Tide. This will scan the class and detect the name of the component from the annotation [Name("hotelsCtl")]. Once registered, we will be able to use tideContext.hotelsCtl to instantiate or get a unique instance of the component.
- The [In] annotation defines a data binding between the variable named 'hotels' in the Tide context and the variable 'hotels' of the mxml component. It's worth noting that it's consequently also bound to the 'hotels' variable in the server Seam context because Tide synchronizes the client and server context at each remote call.
- The application registers itself in the Tide context. This is very important because it allows Tide to intercept the events dispatched by the application, and notify the interested components.
- When the user clicks on the search button, the application dispatches a TideUIEvent. This is a common event class that has a simple string type and can hold parameters. In this case, it holds the search string entered by the user.
- The component also get injected with the 'hotels' context variable. Note that we will always get the exact same collection instance here than in the mxml UI because the source context variable is the same.
- The component get injected with the 'hotelSearch' context variable. We have neither defined a type for this variable nor the name corresponds to a registered Tide component, so Tide will instantiate a common proxy object. Calling any method on this proxy object will trigger a remote call on this method on the server Seam component named 'hotelSearch'.
- The component declares an observer method (annotated with Observer). This method will be called when an event of the expected type (here 'searchForHotels') is dispatched on the context. In this case, it will be called when the user clicks on the search button. The method receives the event parameters as arguments.
- The component sets the 'searchString' property on the 'hotelSearch' proxy, then calls the 'find' method. Tide will transmit the property updates to the server, and execute them on the Seam component. On the server, Tide will set the searchString property on the Seam component 'hotelSearch', then call the 'find' method. 'hotels' is annotated with @DataModel, so Tide intercepts the outjection and sends the value back to the Flex client along the result of the remote call. On the client, Tide finally merges the 'hotels' collection with the one in the Tide context. The visible result for the user is that the DataGrid content is updated, because it is bound to this context variable.
This simple example demonstrates much of the concepts of the Tide framework. Using the context as a central repository for all elements of the application allows a correct decoupling between the UI layer and the application logic, while keeping the amount of classes and code to a minimum. The framework contains a few more features, notably support for client conversations, but keeps relatively lightweight.
Combined with Seam on the server, it does most of the tedious integration work automatically and helps developers to focus on the real application logic.
The Tide sample projects in the GDS distribution do not fully use the framework because they must support Flex 2 compatibility.
Two Flex 3-only projects graniteds_seam_fx3 and graniteds_seam_booking_fx3 have been adapted to benefit from the Tide framework capabilties and are available in the GDS subversion repository at https://granite.svn.sourceforge.net/svnroot/granite.
A more complete description is available in the documentation here.
As always, feedback is more than welcome.
jeudi 16 octobre 2008
GraniteDS Seam AIR Demo
For people wanting to use GDS & AIR, you'll find a sample graniteds_seam_air.zip file here (download graniteds_seam_air.zip).
Some tips:
That's it.
Franck.
Some tips:
- You need to have graniteds-seam-1.1.0.zip installed in Eclipse (get it on Sourceforge here) and deployed in JBoss (started on localhost:8080).
- graniteds_seam_air.zip is an Eclipse/Flex Builder (tested with 3.4.1/3.0.1 versions) project, unzip it in your workspace.
- Use (Flex perspective) "Project" -> "Export Release Build..." and check the "Export and signed an AIR..." option (creating a dummy certificate if you don't have one).
- Install the Person.air application.
That's it.
Franck.
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).
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>
<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 :
Flex code :
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 :
Flex code :
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 :
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 :
MXML code :
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 :
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 :
Flex code :
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 :
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.
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 Listhotels;
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.
Inscription à :
Articles (Atom)