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.