graniteds.orgCommunity Documentation

Chapter 15. Data Management

15.1. JPA and Managed Entities
15.2. Transparent Lazy Loading
15.3. Reverse Lazy Loading
15.4. Dirty Checking and Conflict Handling
15.5. Data Validation
15.6. Data Paging
15.7. Data Push

GraniteDS provides various features that simplify the handling of data between Flex and Java EE, in particular when using JPA or Hibernate as a persistence mechanism.

Tide provides an integration between the Flex/LCDS concept of managed entities and the server persistence context (JPA or Hibernate).

In particular, Tide maintains a client-side cache of entity instances and ensures that every instance is unique in the Flex client context. To achieve this, it requires a unique identifier on each entity class. This is why GraniteDS supports the concept of managed entities.

All entities marked as [Managed] are considered as corresponding to Hibernate/JPA managed entities on the server. It is possible to do the same by implementing IEntity and extending EventDispatcher. The managed entities delegate all operations on their getters/setters to the Tide context to which they belong so all changes can be tracked.

From the Flex documentation of IManaged:

private var _property:String;

public function get property():String {
    return Manager.getProperty(this, "property", _property);
}

public function set property(value:String):void {
    Manager.setProperty(this, "property", _property, _property = value);
}
	   

The Tide Managed implementation itself forwards the getters/setters to the EntityManager/Context containing the entity. As it implements the Flex mx.data.Managed class, it cannot be used in conjunction with LCDS.

It is important to note that the IManaged interface is not sufficient to have correct Tide managed entities because Tide needs that the entities implement a few functionalities that are not provided by the standard Flex IManaged objects.

It is highly recommended to use JPA optimistic locking in a multi-tier environment (@Version annotation). Note that Tide currently only supports Integer or Long version fields, not timestamps and that the field must be nullable (entity instances with a null/NaN version field will be considered as unsaved). It is also highly recommended to add a persistent uid field (generally typed as a 36 bytes String) to have a consistent identifier through all application layers, see the explication below.

Below is a AbstractEntity class that can be used as a JPA mapped superclass for your application entities. The entity listener ensures that the entity always has an initialized uid field, but in general this identifier will be initialized from Flex.



@MappedSuperclass
@EntityListeners({AbstractEntity.AbstractEntityListener.class})
public abstract class AbstractEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Id @GeneratedValue
    private Long id;
    /* "UUID" and "UID" are Oracle reserved keywords -> "ENTITY_UID" */
    @Column(name="ENTITY_UID", unique=true, nullable=false, updatable=false, length=36)
    private String uid;
    @Version
    private Integer version;
    public Long getId() {
        return id;
    }
    public Integer getVersion() {
        return version;
    }
    @Override
    public boolean equals(Object o) {
        return (== this || (instanceof AbstractEntity && uid().equals(((AbstractEntity)o).uid())));
    }
    @Override
    public int hashCode() {
        return uid().hashCode();
    }
    public static class AbstractEntityListener {
        @PrePersist
        public void onPrePersist(AbstractEntity abstractEntity) {
            abstractEntity.uid();
        }
    }
    private String uid() {
        if (uid == null)
            uid = UUID.randomUUID().toString();
        return uid;
    }
}
        

Example build file for ant:



<gas3 outputdir="as3" tide="true">
    <classpath>
        <pathelement location="classes"/>
    </classpath>
    <fileset dir="classes">
        <include name="com/myapp/entity/**/*.class"/>
    </fileset>
</gas3>
        
Important things on ID/UID

In a typical Flex/app server/database application, an entity lives in three layers:

During the entity life, the only invariant is the id. The id reliably links the different existing versions of the entity in the three layers. When updating existing entities coming from the database, there are, in general, no problems because the id is defined and is maintained in the three layers during the different serialization/persistence operations. A problem arises when a new entity is being created in any of the two upper layers (Flex/JPA). The new entity has no id until it has been persisted to the database. This means that between the initial creation and the final stored entity, the id has changed from null to a real value. It is thus impossible to have a reliable link between the original entity that has been created and the entity that has been stored. This is even more complex if you try to add two or more new entities to a collection because, in this case, there will be absolutely no way to determine which one has been persisted with which id because they all had null ids at the beginning. The problem already exists outside of Flex when you use a database generated id with Hibernate/JPA. The most common solution is to have a second persisted id, the uid, which is created by the client and persisted along with the entity. With Flex, we have the same problem because the entities are often serialized/deserialized between Flex and the server, so we cannot check object instances equality. Moreover, Flex components themselves (DataGrids, Lists, ...) use the uid property as an entity identifier to correctly manage different object instances. When there is a uid field in the Java entity, the Gas3 Tide template will generate a uid property on the AS3 object. In other cases, the Tide template tries to build a convenient uid property from the entity id. This second mode is, of course, vulnerable to the initial null id problem. In conclusion, the recommended approach to avoid any kind of subtle problems is to have a real uid property which will be persisted in the database but is not a primary key for efficiency concerns. If it is not possible to add a uid property due to a legacy database schema or Java classes, it will work most of the time but you will then have to be very careful when creating new entities from Flex. You will then have to either implement hashCode() and equals() based on this property, or if you require to have another specific behaviour for hashCode() and equals() you can simply implement the org.granite.tide.IUID Java interface that will instruct Tide to use internally the uid field for object comparisons.

All uninitialized lazy collections coming from the server are transparently wrapped on the Flex side by the Tide context in a PersistentCollection or PersistentMap. This collection can be used as a data provider for any Flex component that is able to handle CollectionEvent (all Flex components, such as DataGrid and List do). When data is requested by the UI component, the collection asks the server for the real collection content. This lazy loading functionality is completely automatic but will happen only if the collection is bound to a UI component.

When in the context of a server Seam conversation with a JPA extended persistence context, Tide will try to load the collection inside this persistence context so all collection elements come from the same persistence context as the owning entity.

Outside of a conversation, Tide will try different means to determine the correct EntityManager/Hibernate session to use. The whole collection and owning entity are then retrieved from a newly created persistence context. If you have a deep object graph, it will then be possible to get entities from different persistence contexts in the same client context, and it can lead to inconsistencies in the client data and issues with optimistic locking/versioning.

Depending on the server framework of the application (Spring, EJB 3, Seam, CDI...), Tide will lookup an EntityManager or an Hibernate session in JNDI, in the Spring context or any other relevant way, and will try to determine the correct transaction management (JTA, JPA...). With Spring or Seam, it is possible to override the default persistence manager if you have particular requirements: with Spring you just have to configure a bean implementing TidePersistenceManager in the application context, with Seam you can override the component named org.granite.tide.seam.seamInitializer with a component extending the class org.granite.tide.seam.seamInitializer. Using a custom persistence manager can be useful for example if you have multiple EntityManagerFactories and want to be able to select one of them depending on the entity whose collection has to be fetched.

Manual Fetching of Lazy Collections

In some cases you may need to trigger manually the loading of a lazy loaded collection. As told earlier, all collections are wrapped in a PersistentCollection or PersisteneMap. These two classes expose a method withInitialized that can take a function callback that can do something once the collection is populated:

Object(myEntity.myCollection).withInitialized(function(collection:IList):void {
   // Do something with the content of the list
   var obj:Object = collection.getItemAt(0);
});
	   

Lazy loading greatly helps limiting the amount of data that is transferred between the server and the client. When receiving data from the server, the lazy-loading state is managed by the JPA engine depending on what is done by the service, so only the necessary data is sent. When sending objects to the server, the lazy-loading state depends on what has been or not loaded on the client context. As the lifespan of the entities in the client context is much longer than in stateless services, there is a good chance that after a few time of running application, the whole object graph will be loaded on the client. That means that passing an entity as an argument to a remote method call will send the fully loaded object graph, whenever maybe a single property has been modified on the main entity. For example :



public function savePerson():void {
    person.lastName = "Test";
    personService.save(person);   // This will send all loaded collections associated to the Person object
}
       

Obviously this is not very efficient. You can now ask Tide to limit the object graph before sending it. It can be done manually in Flex with the following API :

var uperson:Person = new EntityGraphUnintializer(tideContext).uninitializeEntityGraph(person) as Person;
personService.save(uperson);    // This will send only the Person object without any loaded collection
       

Here all loaded collections of the Person object will be uninitialized so uperson contains only the minimum of data to correctly merge your changes in the server persistence context. If there is a change deeper in the object graph, the uninitializer is able to detect it and will not uninitialize the corresponding graph so the server receives all changes.

person.contacts.getItemAt(0).email = 'test@test.com';
var uperson:Person = new EntityGraphUnintializer(tideContext).uninitializeEntityGraph(person) as Person;
personService.save(uperson);    // This will send the Person object with only the contacts collection loaded
       

Tide uses the client data tracking (the same used for dirty checking, see below) to determine which parts of the graph need to be sent.

If you need to uninitialize more than one argument for a remote call, you have to use the same EntityGraphUninitializer. It is important because the uninitializer copies the entities in a temporary context before uninitializing their associations so the normal context keeps unchanged, and all processed entities have to share this same temporary context.

var egu:EntityGraphUnintializer = new EntityGraphUninitialize(tideContext);
uperson1 = egu.uninitializeEntityGraph(person1);
uperson2 = egu.uninitializeEntityGraph(person2);
personService.save(uperson1, uperson2);
        

Calling the EntityGraphUninitializer manually is a bit tedious and ugly, so there is a cleaner possibility when you are using generated typesafe service proxies. You can annotate your service method arguments with @org.granite.tide.data.Lazy :



public void save(@Lazy Person person) {
}
        

Gas3 will then generate a [Lazy] annotation on your service methods (so take care that you have added the [Lazy] annotation to your Flex metadata compilation configuration). Next in the Flex application, register the UninitializeArgumentPreprocessor component in Tide as follows :

Tide.getInstance().addComponents([UninitializeArgumentPreprocessor]);
        

Once you have done this, all calls to PersonService.save() will automatically use a properly uninitialized version of the person argument.

This new feature cannot be yet applied to local context variables (mostly used with Seam or CDI stateful components). Only method arguments can be processed, but this should cover be the vast majority of use cases. Support for context variables will come with GDS 3.0.

The Tide framework includes a client-side entity cache where each managed entity exists only once for each Tide context. Besides maintaining this cache, Tide tracks all changes made on managed entities and on their associations and saves these changes for each modification. This flag is always reset to false when the same instance is received from the server, so this flag is indeed an indication that the user has changed something since the last remote call.

A particular entity instance can be in two states :

The current state of an entity can be accessed with :

entity.meta_dirty
	   

The property meta_dirty is bindable, so it could be used for example to enable/disable a Save button.

Note that this dirty flag only indicates if a direct property or collection of the entity has been changed, it does not indicate if something has changed deeper in the object graph (that would not make sense anyway for circular graphs). The correct way of knowing if any object has been changed in the context, is to use the property meta_dirty of the Tide context.



<mx:Button label="Save" click="entityService.save()"
    enabled="{tideContext.meta_dirty}"/>
       

In a typical client/server interaction, here is what happens :

Note that if you retrieve the same instance without version increment, the local changes won't be overwritten. In the previous example, if the server returns the same instance with an unchanged version number of 0, the local instance will still be dirty. That means that you can still issue queries that return a locally changed entity without losing the user changes.

One nice possibility with this programming model is that you can easily implement a cancel button after step 2. If you use bidirectional data binding, the client view of the entity instance has already become dirty. As Tide always saves the local changes, it also provides a simple way of restoring the last stable state :

private function restore():void {
    Managed.resetEntity(entity);
}
   	   

You can also reset all entities in the context to their last stable state with :

private function restoreAll():void {
    tideContext.meta_resetAllEntities();
}
   	   

If you look at the previous process in 3 steps, we assume that nobody else has changed the data the user has been working on between 1 and 3. In highly concurrent environments with read-write data, there are possibilities that someone else has modified the entity on the server between step 1 and step 3.

There are two ways of managing this: either you just rely on optimistic locking and intercept the corresponding server exceptions to display a message to the user, or you use data push (see section Data Push) so all Flex clients are updated in near real-time. Note however that even with data push, there can still be conflicts between changes made by a user and updates received from the server.

With normal optimistic locking, the remote service call at step 3 will trigger a OptimisticLockException. Tide provides a built-in exception handler to handle this case: it will extract the entity argument of the exception, compare its state with the client state and dispatch a conflict event TideDataConflictEvent on the Tide context when it's not identical. The exception handler can be enabled with :

Tide.getInstance().addExceptionHandler(OptimisticLockExceptionHandler);
   	   

When data push is used, an entity instance can be updated with data received from the server at any time. If the current user was working on this instance, it is obviously not desirable that his work is overwritten without notice. Similarly to the previous case, Tide will determine that an incoming data from another user session is in conflict with the local data and dispatch a TideDataConflictEvent on the Tide context.

What can you do with this event ? Basically there are two possibilities : accept the server-side state or keep the client state. Here is an example of a conflict handler defined in a Flex application, generally in the main mxml :



<mx:Application ...
    preinitialize="Tide.getInstance().initApplication()"
    creationComplete="init();">
    
    <mx:Script>
        private function init():void {
            Tide.getInstance().getEjbContext().addEventListener(
                TideDataConflictsEvent.DATA_CONFLICTS, conflictsHandler);
        }

        private var _conflicts:Conflicts;

        private function conflictsHandler(event:TideDataConflictsEvent):void {
            _conflicts = event.conflicts;
            Alert.show("Keep local state ?", "Data conflict", 
                Alert.YES|Alert.NO, null, conflictsCloseHandler);
        }
        
        private function conflictsCloseHandler(event:CloseEvent):void {
            if (event.detail == Alert.YES)
                _conflicts.acceptAllClient();
            else
                _conflicts.acceptAllServer();
        }
    </mx:Script>
    ...
</mx:Application>
       

If you look at the ASDoc for Conflicts, there are a few properties that give more details about the conflicts and make possible to present a better alert message to the user.

When using the Hibernate native API (Session), the optimistick lock exception StaleObjectStateException is unfortunately missing a critical information to allow for correct conflict handling (that is present in the JPA OptimistickLockException). In this case, you should use the provided Hibernate event wrappers that add the missing data to the Hibernate exception. Here is what is will look like when configuring the SessionFactory with Spring :



<bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
            <prop key="hibernate.show_sql">false</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
        </props>
    </property>
    <property name="eventListeners">
        <map>
            <entry key="merge"><bean class="org.granite.tide.hibernate.HibernateMergeListener"/></entry>
            <entry key="create"><bean class="org.granite.tide.hibernate.HibernatePersistListener"/></entry>
            <entry key="create-onflush"><bean class="org.granite.tide.hibernate.HibernatePersistOnFlushListener"/></entry>
            <entry key="delete"><bean class="org.granite.tide.hibernate.HibernateDeleteListener"/></entry>
            <entry key="update"><bean class="org.granite.tide.hibernate.HibernateSaveOrUpdateListener"/></entry>
            <entry key="save-update"><bean class="org.granite.tide.hibernate.HibernateSaveOrUpdateListener"/></entry>
            <entry key="save"><bean class="org.granite.tide.hibernate.HibernateSaveOrUpdateListener"/></entry>
            <entry key="lock"><bean class="org.granite.tide.hibernate.HibernateLockListener"/></entry>
            <entry key="flush"><bean class="org.granite.tide.hibernate.HibernateFlushListener"/></entry>
            <entry key="auto-flush"><bean class="org.granite.tide.hibernate.HibernateAutoFlushListener"/></entry>
        </map>
    </property>
    ...
</bean>
       
Integration with Client Conversations

When using client conversations, the situation becomes a bit more complex as each conversation has its own entity cache. That means that when a user modifies some data in a conversation context, the change is not immediately visible in others even if they contain the same entity instance.

The Tide context object provides a few methods to deal with such situations :

tideContext.meta_mergeInParentContext()
   	   

This will merge all stable state of the current conversation context in its parent context and all its children. In the common case where you don't use nested conversations, that just means that the changes are merged in the global context as well as all other conversations.

If you use nested conversations, the merge will be done only in the direct parent context and all its children, but not in the global context.

tideContext.meta_mergeInGlobalContext()
       

This will merge all stable state of the current conversation context in its parent context, in the global context and in all child contexts recursively.

Tide integrates with Hibernate Validator 3.x and the Bean Validation API (JSR 303) implementations, and propagate the server validation errors to the client UI components.

Starting with GraniteDS 2.2, however, the preferred way to execute validation is a Flex-side, JSR-303 like, validation framework (see Bean Validation (JSR-303) for details).

The server support for Hibernate Validator 3 is available in granite-hibernate.jar, and the support for Bean Validation is available in granite-beanvalidation.jar. You will have to add one of these jars in your application lib folder.

The validator integration is based on the GraniteDS exception handling framework. A server exception converter is registered to handle the InvalidStateException, and a client exception handler can be registered with:

Seam.getInstance().addExceptionHandler(ValidatorExceptionHandler);
	   

This exception handler intercepts all server validation faults and dispatches a validation event on the context. To complete the integration, a TideEntityValidator Flex component can be attached to any UI component:



<mx:Application
    ...
    xmlns:tv="org.granite.tide.validators.*">

    <tv:TideEntityValidator id="teval" entity="{tideContext.booking}"
        property="creditCardName" listener="{creditCardNameInput}"/>

    <mx:TextInput id="creditCardNameInput" text="{tideContext.booking.creditCardName}"/>
    ...
</mx:Application>
       

You can have more control on the validation behaviour by directly listening to validation events on the context:

public function setup():void {
    tideContext.addEventListener(TideValidatorEvent.INVALID, validationHandler);
}

private function validationHandler(event:TideValidatorEvent):void {
    var invalidValues:Array = event.invalidValues;
    for each (var iv:Object in invalidValues) {
        var invalidValue:InvalidValue = iv as InvalidValue;
        Alert.show(
            "Invalid property: " + invalidValue.path +
            " on object " + invalidValue.bean
        );
    }
}
	   
Remote Validation of Input Fields

Another possibility is to use an input validator that calls the server when the validation is triggered.

With Seam on the server, it then uses the Validators component to get a proper ClassValidator, and thus just works with Hibernate Validator 3.x for now. With other server technologies, it uses a built-in validator handler which supports both HV3 and Bean Validation implementations.

The use of this component is quite simple and very similar to any other Flex validator, with additional parameters to define the entity to validate.



<tv:TideInputValidator id="tival" 
    source="{creditCardNameInput}" property="text" 
    entity="{tideContext.booking}" entityProperty="creditCardName" 
    entityManager="{tideContext}"/>

<mx:TextInput id="creditCardNameInput" text="{tideContext.booking.creditCardName}"/>
       

The property entityManager of the validator may be optional if the entity is maintained in the context (i.e., ctx.myEntity) and has a proper entityManager defined (with meta_getEntityManager). This is the case for all entities retrieved from the server.

GraniteDS provides the PagedQuery component which is an implementation of ListCollectionView and can be used as a data provider for most UI components such a grids or lists.

This component supports paging and can be mapped to a server component which execute queries. The collection is completely paged and keeps in memory only the data needed for the current display. In fact, it keeps in memory two complete pages to avoid too many server calls. Contraty to other implementations, the elements that get out of display during scrolling are discarded, so you never fill up the memory of the Flash VM.

PagedQuery also supports automatic remote sorting and filtering. The server-side part of the paging depends on the server technology and is described in the next paragraphs.

On the client-side, you first need to register the client component with:



<mx:Script>
    import org.granite.tide.collections.PagedQuery;

    Tide.getInstance().addComponent("people", PagedQuery);
</mx:Script>      
        

This registers a client component with a page size defined by the server. It's also possible to define the page size on the client with :



<mx:Script>
    import org.granite.tide.collections.PagedQuery;

    Tide.getInstance().addComponentWithFactory("people", PagedQuery, { maxResults: 36 });
</mx:Script>      
        

When using the Tide client framework, that can be done in a Tide module initializer as any other component declaration.

That's all. Just bind the component as a data provider for any component and it should work as expected:



<mx:DataGrid id="people" dataProvider="{ctx.people}" width="100%" height="100%"
    liveScrolling="false">
    <mx:columns>
        <mx:DataGridColumn dataField="firstName" headerText="First name"/>
        <mx:DataGridColumn dataField="lastName" headerText="Last name"/>
    </mx:columns>
</mx:DataGrid>
        

The DataGrid sorting triggers a remote refresh of the collection, and the changes on the data filter are maintained during the remote refresh, so the filtering is also done remotely.



<s:List>
    <mx:AsyncListView list="{people}"/>
</s:List>
        

See here for more details in the Flex 4 documentation.

Just like the paged collections of LCDS, everything works only if there is a correct uid property on the entities. It is thus recommended to use the Tide templates for the Gas3 generator, which provide a default implementation of uid if there is none.

Server-side Implementation

The PagedQuery components expects that the corresponding server component implements a specific method to fetch elements. There are two ways of handling filtering, either with an untyped map or with a typesafe filter object:

For untyped filters, the server component shoud implement the following method:



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.pagedQuery.filter.<parameter1> = <value1>;
tideContext.pagedQuery.filter.<parameter2> = <value2>;
...
        

The return object must be a map containing four properties:

  • firstResult: Should be exactly the same as the argument passed in (int first).
  • maxResults: Should be exactly the same as the argument passed in (int max), 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, which must absolutely be greater than the maximum expected number of elements displayed simultaneously in a table.
  • resultCount: Count of results.
  • resultList: List of results.

The following code snippet is a quick and dirty implementation and can be used as a base for other implementations (here this is a Spring service but the equivalent implementations for EJB3 or CDI would be extremely similar):



@Service("people")
@Transactional(readOnly=true)
public class PeopleServiceImpl implements PeopleService {
    @PersistenceContext
    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 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;
    }
}
        

It is also possible to define on the Flex size an alternative remote component name and method name that will implement the querying :



<mx:Script>
    Spring.getInstance().addComponentWithFactory("people", PagedQuery, 
        { maxResults: 36, remoteComponentName: "peopleService", methodName: "list" });
</mx:Script>
        

In this case, the previous component would be :



@Service("peopleService")
@Transactional(readOnly=true)
public class PeopleServiceImpl implements PeopleService {
    @PersistenceContext
    protected EntityManager manager;
    public Map list(Map filter, int first, int max, String order, boolean desc) {
        Map result = new HashMap();
        String from = "from Person 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("resultCount", resultCount);
        result.put("resultList", resultList);
        return result;
    }
}
        

Note that this time we did not have to return firstResult and maxResults because the page size is defined on the client.

It is finally possible to use a typesafe filter object instead of the Map. The server implementation will then be something like :



@Service("peopleService")
@Transactional(readOnly=true)
public class PeopleServiceImpl implements PeopleService {
    @PersistenceContext
    protected EntityManager manager;
    public Map list(Person examplePerson, int first, int max, String order, boolean desc) {
        Map result = new HashMap();
        String from = "from Person e ";
        String where = "where lower(e.lastName) like '%' || lower(:lastName) || '%' ";
        String orderBy = (
            order != null ? "order by e." + order + (desc ? " desc" : "") : ""
        );
        String lastName = (
            examplePerson.getLastName() != null ? examplePerson.getLastName() : ""
        );
        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("resultCount", resultCount);
        result.put("resultList", resultList);
        return result;
    }
}
        

To use this, you also have to define the filter class on the client component :



<mx:Script>
    import org.granite.tide.collections.PagedQuery;

    Tide.getInstance().addComponentWithFactory("people", PagedQuery, { maxResults: 36, filterClass: Person });
</mx:Script>      
        

If the filter class is bindable, then people.filter will be an instance of the provided filter class. If not, the PagedQuery will create an ObjectProxy that wraps the filter instance to track changes on it.

You can also directly provide your own instance of the filter instead of letting the component instantiate the class itself:



<mx:Script>
    import org.granite.tide.collections.PagedQuery;

    Tide.getInstance().addComponentWithFactory("people", PagedQuery, { maxResults: 36, filter: new Person() });
</mx:Script>      
        

In classic Flex applications using remoting, data is updated only when the user does an action that triggers a call to the server. As it is possible to do many things purely on the client without involving the server at all, that can lead to stale client state if someone else has modified something between updates.

Optimistic locking ensures that the data will keep consistent on the server and in the database, but it would be better if data updates were pushed in real-time to all connected clients.

Tide makes this possible by integrating with the JPA provider and the Gravity messaging broker to dispatch data updates to subscribed clients.

This requires a bit of configuration :

Let's see all this in details :

Define a Gravity topic: in the standard case, it can be done in services-config.xml:



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

    <destination id="dataTopic">
        <properties>
            <no-local>true</no-local>
            <session-selector>true</session-selector>
        </properties>
        <channels>
            <channel ref="gravityamf"/>
        </channels>
        <adapter ref="simple"/>
    </destination>
</service>
...
<channel-definition id="gravityamf" class="org.granite.gravity.channels.GravityChannel">
    <endpoint
        uri="http://{server.name}:{server.port}/{context.root}/gravityamf/amf"
        class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
        

With Spring or Seam, this can be done more easily in the respective configuration files application-context.xml or components.xml:

Spring context:



<graniteds:messaging-destination id="dataTopic" no-local="true" session-selector="true"/>
        

Seam components.xml:



<graniteds:messaging-destination name="dataTopic" no-local="true" session-selector="true"/>
        

This example configuration defines a simple Gravity destination but it's also possible to use the JMS, ActiveMQ or any custom adapter if you need transactional behaviour or better scalabilty.

The two important parameters for the topic definition are :

Add the Tide JPA publishing listener on the entities that should be tracked:



@Entity
@EntityListeners({DataPublishListener.class})
public abstract class MyEntity {
    ... 
}
        

Add the Tide data annotation on all services, example here with a Spring service:



@DataEnabled(topic="dataTopic", publish=PublishMode.ON_SUCCESS)
public interface MyService {
    ...
}
        

It's generally recommended to put the annotation on the service interface but it can also work when defined on the service implementation. Note that even services that only read data should be annotated this @DataEnabled because they also participate in the construction of the message selector.

The attributes of this annotations are :

Publishing Filters

It is possible to tell the Tide engine how it should dispatch each update (i.e. to which clients).

It works in two phases : at each remote call from a client, Tide calls the observes method of the params class and builds the current message selector. Next at each update it calls publishes to set the message headers that will be filtered by the selector. Let's see it on an example to be more clear :



public class AddressBookParams implements DataTopicParams {
    
    public void observes(DataObserveParams params) {
        params.addValue("user", Identity.instance().getCredentials().getUsername());
        params.addValue("user", "__public__");
    }
    
    public void publishes(DataPublishParams params, Object entity) {
        if (((AbstractEntity)entity).isRestricted())
            params.setValue("user", ((AbstractEntity)entity).getCreatedBy());
        else
            params.setValue("user", "__public__");
    }
}
        

The method observes here adds two values to the current selector: the current user name (here retrieved by Seam Identity but could be any other means) and the value __public__. From these values Tide will define a message selector (user = 'username' OR user = '__public__') meaning that we only want to be notified of updates concerning public data or data that we own.

During the publishing phase, Tide will call the method publishes for each updated entity and build the message headers with the provided values. In the example, an update message will have a user header with either __public__ or the entity owner for restricted data. These headers are then matched with the current message selector for each subscribed client.

Here we have used only one header parameter but it's possible to define as many as you want. Just take care that the match between observed and published values can become very complex and difficult to predict with too many criteria. When having many header values, the resulting selector is an AND of all criteria :



public void observes(DataObserveParams params) {
    params.addValue("user", Identity.instance().getCredentials().getUsername());
    params.addValue("user", "__public__");
    params.addValue("group", "admin");
    params.addValue("group", "superadmin");
}
        

Will generate the following selector :

(user = 'username' OR user = '__public__') AND (group = 'admin' OR group = 'superadmin')
        
Publishing Modes

There are three publishing modes :

By default only GraniteDS remoting calls are able to dispatch update messages with ON_SUCCESS or MANUAL modes. If you need the ON_COMMIT mode, or need that services that are not called from Flex also trigger the dispatch, then you will have to enable the Tide data dispatcher interceptor that will handle to updates in threads that are not managed by GraniteDS.

To enable the interceptor, it is necessary to indicate on the @DataEnabled annotation that there is one with the useInterceptor attribute :



@DataEnabled(topic="dataTopic", publishMode=PublishMode.ON_COMMIT, useInterceptor=true)
public class MyService {
}
        

There are four versions of the interceptor available for each supported framework : EJB3, Spring, Seam 2, CDI.

For Spring, add the advice to your context (take care that you need to reference the latest GraniteDS XSD version 2.3 to allow this) :



<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:graniteds="http://www.graniteds.org/config"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.graniteds.org/config http://www.graniteds.org/public/dtd/2.3.0/granite-config-2.3.xsd">
    ...
<graniteds:tide-data-publishing-advice/>            
        

For Seam 2, there is nothing special to configure. The interceptor will be automatically enabled for all components having the @DataEnabled annotation with useInterceptor=true.

For CDI, enable the interceptor in beans.xml :



<beans
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">  
    <interceptors>
        <class>org.granite.tide.cdi.TideDataPublishingInterceptor</class>
    </interceptors>
</beans>            
        

For EJB 3, you can define a global interceptor in ejb-jar.xml :



<assembly-descriptor>
      <interceptor-binding>
         <ejb-name>*</ejb-name>
         <interceptor-class>org.granite.tide.ejb.TideDataPublishingInterceptor</interceptor-class>
      </interceptor-binding>
      ...
</assembly-descriptor>
        

Or alternatively configure the interceptor on each EJB 3 :



@Stateless
@Local(MyService.class)
@Interceptors(TideDataPublishingInterceptor.class)
@DataEnabled(topic="myTopic", publish=PublishMode.ON_COMMIT, useInterceptor=true)
public class MyServiceBean {
    ...
}
        
Manual Publishing

If you need full control on the publishing process, you can create your own interceptor or use the following API in your services :



@DataEnabled(topic="dataTopic", params=DefaultDataTopicParams.class, publishMode=PublishMode.MANUAL, useInterceptor=true)
public class MyService {
    @Inject
    private Gravity gravity;
    public void doSomething() {
        DataContext.init(gravity, "dataTopic", DefaultDataTopicParams.class, PublishMode.MANUAL);
        try {
            Object result = invocation.proceed();
            DataContext.publish(PublishMode.MANUAL);
            return result;
        }
        finally {
            DataContext.remove();
        }
    }
}
        


@Interceptor
public class CustomPublishInterceptor {
    @Inject
    private Gravity gravity;
    @AroundInvoke
    public Object aroundInvoke(InvocationContext invocation) throws Exception {
        DataContext.init(gravity, "dataTopic", DefaultDataTopicParams.class, PublishMode.MANUAL);
        try {
            Object result = invocation.proceed();
            DataContext.publish(PublishMode.MANUAL);
            return result;
        }
        finally {
            DataContext.remove();
        }
    }
}
        
Transactional Publishing

You can setup a fully transactional dispatch by using the ON_COMMIT mode with a JMS transport. When using JMS transacted sessions with the ON_COMMIT mode, you will ensure that only successful database updates will be dispatched.



<destination id="dataTopic">
    <properties>
        <jms>
            <destination-type>Topic</destination-type>
            <connection-factory>ConnectionFactory</connection-factory>
            <destination-jndi-name>topic/dataTopic</destination-jndi-name>
            <destination-name>dataTopic</destination-name>
            <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
            <transacted-sessions>true</transacted-sessions>
            <no-local>true</no-local>
        </jms>
        <no-local>true</no-local>
        <session-selector>true</session-selector>
    </properties>
    <channels>
        <channel ref="gravityamf"/>
    </channels>
    <adapter ref="jms"/>
</destination>