vendredi 9 janvier 2009

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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

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

vendredi 26 décembre 2008

What's new in GraniteDS 1.2

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

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

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

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

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

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

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

mardi 2 décembre 2008

GraniteDS at Devoxx 2008

Hi all,

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

Come and join us!

Regards,
Franck.

jeudi 20 novembre 2008

Support for Seam 2.1

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

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

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

Most implementation classes are different :

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

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

services-config.xml is exactly the same.

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

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

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

<!-- Seam -->

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

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

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

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

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

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


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

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

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

mercredi 19 novembre 2008

GraniteDS Tide for Spring

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

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

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

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

First the inevitable Hello World :

Spring bean (2.5 style with annotations) :

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


Flex code :

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

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

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


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

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


Entity caching

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

Spring bean (2.5 style with annotations) :

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

@PersistenceContext
private EntityManager manager;

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


Flex code :

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

[Bindable]
public var person:Person;

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

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


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


The other particular case is the one of collections :

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

@PersistenceContext
private EntityManager manager;

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


Flex code :

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

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

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

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


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

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


Data paging

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

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

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


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

The following code snippet is a quick and dirty implementation :

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

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


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



Lazy loading of collections

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

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


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


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

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

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



Integration with Hibernate Validator

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


Conclusion

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

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 :
  • 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 List hotels;

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 List getHotels() {
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();
}
}
  1. The main application initializes and gets the Tide context.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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'.
  8. 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.
  9. 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:

  1. 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).

  2. graniteds_seam_air.zip is an Eclipse/Flex Builder (tested with 3.4.1/3.0.1 versions) project, unzip it in your workspace.

  3. 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).

  4. Install the Person.air application.


That's it.
Franck.