mardi 1 décembre 2009

Advanced data features of the Tide framework

Two powerful features of the Tide framework are demonstrated in the example projects but very poorly documented (or not documented at all) : collaborative data updates and concurrent updates conflicts detection/resolution.

Collaborative data updates is the ability to transparently push updates made on an entity by a user to all other interested users. Here is how it works :

  • First tracked entities have to be listened by the DataPublishListener entity listener. If all entities need to be tracked, just put the listener on some AbstractEntity mapped superclass :
    @Entity
    @EntityListeners({DataPublishListener.class})
    public class MyEntity {
    ...
    }

  • Define a Gravity topic to push the updates :
    For example with Seam or Spring, just add this in your configuration file :
    <graniteds:messaging-destination id="myTopic" no-local="true" session-selector="true"/>

    The important thing is to specify the session selector parameter that will allow Tide to store the Gravity/JMS selector in the user client session. It's also useful to set no-local so a client does not receive its own updates. In the classic services-config.xml configuration file, just use :
    <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="myTopic">
    <properties>
    <no-local>true</no-local>
    <session-selector>true</session-selector>
    </properties>
    <channels>
    <channel ref="my-gravityamf"/>
    </channels>
    <adapter ref="simple"/>
    </destination>
    </service>
    If transactions or high scalabilty are needed it can work also with a JMS or ActiveMQ topic.

  • All server components annotated with @DataEnabled are intercepted by Tide and the annotation parameters are used to determine the push configuration (topic and selection of pushed clients).
    @DataEnabled(topic="myTopic", params=MyParams.class, publish=PublishMode.ON_SUCCESS)
    public class MyComponent {
    ...
    }

  • PublishMode.ON_SUCCESS indicates that Tide will automatically push the updates for all successful remote calls. PublishMode.ON_COMMIT is not available for now but will be implemented later to get transactional behaviour in JEE environments. The default is PublishMode.MANUAL that requires to call the push method manually with :
    DataContext.publish();

  • The params class defines which clients will receive which updates. It internally uses JMS-like selectors to route messages. Let's see the simplest possible one :
    public class ObserveAllPublishAll implements DataTopicParams {
    @Override
    public void observes(DataObserveParams params) {
    }

    @Override
    public void publishes(DataPublishParams params, Object entity) {
    }
    }
    The observes method indicates which criteria will be used to determine what the client will receive. Here we set no criteria so by default all clients will receive everything. The publishes method indicates the value of the criteria for the update of the specified entity. Here again we set nothing so every update will be pushed to everyone.
    Here is a more interesting example :
    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 observes method indicates that we are interested in getting all updates marked public or made by the current user (all values are appended with a OR operator to the client selector). The publishes method defines the user parameter when the entity is marked as restricted. All this means that a client will receive only public updates or updates made by himself on its own private entities. It's possible to use as many criteria parameters as you want, just ensure that observes and publishes are consistent.

  • Once all this server-side configuration is done, there is still to define a client-side data observer to receive the updates :
    Tide.getInstance().addComponent("myTopic", DataObserver);
    Tide.getInstance().addEventObserver("org.granite.tide.login", "myTopic", "subscribe");
    Tide.getInstance().addEventObserver("org.granite.tide.logout", "myTopic", "unsubscribe");
    This just registers a DataObserver component with the same name as the topic. The events are used to automatically subscribe/unsubcribe the observer on user login/logout.


  • It's a relatively complex setup but once done you don't have anything else to do to automatically get all clients transparently updated when any user modifies something.


    Now that you have this working, you will probably try to update the same entity from two different browsers and see that the result in the database is unpredictable. GraniteDS 2.1 comes with a conflict handling mechanism that should help with these cases. The main requirement is to enable optimistic locking on your JPA entities with a numeric @Version field.

    Then you just have to define a conflict handler on the global context with :
    private function init():void {
    Tide.getInstance().getContext().addEventListener(TideDataConflictsEvent.DATA_CONFLICTS, conflictsHandler);
    }

    private var _conflicts:Conflicts;

    private function conflictsHandler(event:TideDataConflictsEvent):void {
    _conflicts = event.conflicts;
    Alert.show("Someone else has modified the entity you are currently working on.\nDo you want to keep your modifications ?",
    "Modification conflict", Alert.YES | Alert.NO, null, conflictsCloseHandler);
    }

    private function conflictsCloseHandler(event:CloseEvent):void {
    if (event.detail == Alert.YES)
    _conflicts.acceptAllClient();
    else
    _conflicts.acceptAllServer();
    }


    This is relatively straighforward: a conflict event is dispatched whenever conflicts are detected during merge of the pushed updates. Then it's possible either to keep local changes with acceptAllClient, or to get latest server data with acceptAllServer.

    If you don't use data push, it's still possible to use this conflict handling by detecting server-side OptimisticLockException. All Tide service factory install by default a PersistenceExceptionConverter that understands this exception and translates it to something that can be used on the client.
    On the client, just register the corresponding exception handler with :
    Tide.getInstance().addExceptionHandler(OptimisticLockExceptionHandler);
    Then all optimistic lock errors will be considered as local conflicts and will use the previously described conflict handling mechanism. That will allow the end user to keep its change or get the last data from the server.

    Note that this API is not necessarily final so your feedback would be very welcome to improve it or add missing features.

    jeudi 5 novembre 2009

    New in GraniteDS 2.1.0 RC1: early support for JSR-299 / JCDI

    GraniteDS has almost always had support for EJB3 and JBoss Seam. With the advent of JEE6, support for JSR-299 / JCDI is quite a natural evolution and probably a must have in a not so far future.

    So what's in this first implementation :

  • Tide remoting to JCDI Named components

  • All features supported for EJB3: integration with container security, paging, lazy loading

  • Support for JCDI conversations

  • Support for client-side Flex event observers of JCDI typed events


  • Note that for now it will work only with the Weld implementation, as conversation support requires using some non public JSR-299 API.

    Also the supported containers are for now JBoss 5.2 trunk (available here and GlassFish V3 starting from build 70 (available here). It will probably not work in Tomcat for now.

    The GraniteDS distribution contains a graniteds-tide-jcdi example that can be deployed in any of these application servers.

    Configuration

    The configuration is almost the same as for other server framework integrations and consists in five parts :

  • Add the GDS libraries in WEB-INF/lib : granite.jar, granite-jcdi.jar and granite-hibernate.jar (or granite-eclipselink.jar for GlassFish)

  • Add the AMF (and Gravity if needed) servlets in web.xml

  • Add the following granite-config.xml in WEB-INF/granite :
  • <granite-config scan="true">
    <security type="org.granite.messaging.service.security.TomcatSecurityService"/>

    <tide-components>
    <tide-component instance-of="org.granite.tide.jcdi.JCDIIdentity"/>
    <tide-component annotated-with="org.granite.messaging.service.annotations.RemoteDestination"/>
    </tide-components>
    </granite-config>

  • Configure the Tide JCDI service factory in services-config.xml in WEB-INF/flex :
  • <factories>
    <factory id="tideJcdiFactory" class="org.granite.tide.jcdi.JCDIServiceFactory"/>
    </factories>

  • Add an empty beans.xml in WEB-INF

  • Remoting

    Once this is done, add your named JCDI bean and annotate it with @RemoteDestination :

    @Named("helloWorld")
    @RemoteDestination(id="helloWorld")
    public class HelloWorld {

    public String hello(String name) {
    return "hello" + name;
    }
    }

    Then you can easily call it from Flex using Tide remoting and injection :

    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    preinitialize="Jcdi.getInstance().initApplication()">

    <mx:Script>
    <![CDATA[
    import org.granite.tide.jcdi.Jcdi;
    import org.granite.tide.Component;

    [In]
    public var helloWorld:Component;
    ]]>
    </mx:Script>

    <mx:Button label="Hello" click="helloWorld.hello('Barack')"/>

    </mx:Application>

    You can even use typesafe client proxies (e.g. public var helloWorld:HelloWorld) if you have generated them with gas3. Maybe in RC2 we'll try to use completely typesafe service invocation and will not require @Named beans any more.

    Events

    Support for events is relatively similar to what exists for Seam, but with JCDI it uses typesafe events.
    Define your Java and as3 event classes (in the final release it will be possible to generate the as3 event class automatically with gas3) :
    public class GreetingEvent {
    private String name;

    public GreetingEvent(String name) {
    this.name = name;
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    }
    [RemoteClass(alias="test.app.GreetingEvent")]
    public class GreetingEvent extends AbtractTideEvent {
    public var name:String;
    }

    Fire an event from the server method :
    @Named("helloWorld")
    @RemoteDestination(id="helloWorld")
    public class HelloWorld {

    @Inject @Any
    Event greetingEvent;

    public String hello(String name) {
    greetingEvent.fire(new GreetingEvent(name));
    return "hello" + name;
    }
    }

    Then just declare a remote observer in the Flex application and it will be triggered whenever the event is dispatched during a remote call initiated from Flex.
    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    preinitialize="Jcdi.getInstance().initApplication()">

    <mx:Script>
    <![CDATA[
    import org.granite.tide.jcdi.Jcdi;
    import org.granite.tide.Component;

    [In]
    public var helloWorld:Component;

    [Observer(remote="true")]
    public function greet(event:GreetingEvent):void {
    trace("Greeting to " + event.name);
    }
    ]]>
    </mx:Script>

    <mx:Button label="Hello" click="helloWorld.hello('Barack')"/>

    </mx:Application>



    This is really just an early implementation of this integration and it will be updated for the final release of the JCDI specification and RI.

    Our feeling is that JCDI is a perfect fit for Flex RIAs with an event-driven architecture. JCDI applications looks extremely clean and even JBoss Seam provides a lot more features, they do not necessarily make sense with a RIA front-end.

    Feel free to give some feedback and maybe some ideas for this integration.

    New in GraniteDS 2.1.0 RC1: new features of the gas3 generator

    Typesafe proxies


    One of the very nice new features in GraniteDS 2.1 has been contributed by Francesco Faraone and Saverio Trioni.

    If you have already used RemoteObject or the Tide Component/Object API to access remote services, you most likely already have noticed that neither of these options are typesafe.
    What this means is that you can't have code completion in your Flex IDE, and that you can easily make a mistake in either the remote method name or its arguments.

    With GDS 2.1, the gas3 generator is now able to generate typesafe client proxy classes from Java service classes or interfaces. While it can work with service implementations, it's much better to generate the client proxies from service interface when possible. Let's see how this works with the gas3 ant task :

    <target name="generate.as3" depends="define.gas3"
    description="Generate AS3 beans and proxies for example entities and services">

    <gas3 outputdir="flex">
    <classpath>
    <pathelement location="classes"/>
    <path dir="lib"/>
    </classpath>
    <fileset dir="classes">
    <include name="com/myapp/entities/**/*.class"/>
    </fileset>
    <fileset dir="classes">
    <include name="com/myapp/services/**/*Service.class"/>
    </fileset>
    </gas3>
    </target>


    As you can see, this is no different from generating as3 entity beans, just include the interface classes in the generator fileset.

    It's not the only thing to do, because the generator needs to know that is has to generate a client proxy for our interface and not only an as3 interface. So you have to annotate your service interface with @RemoteDestination(id="myService"). If you use GraniteDS for remoting, that should be generally already the case. Here's an example service interface :

    @RemoteDestination(id="personService")
    public interface PersonService {

    public Person createPerson(Person person);

    public Person modifyPerson(Person person);

    public void deletePerson(Integer personId);
    }


    When generating the client for this interface, you will get a PersonService as3 class that extends RemoteObject and can be used exactly as a RemoteObject :

    var personService:PersonService = new PersonService();
    personService.addOperationListener(personService.modifyPerson, ResultEvent.RESULT, handlerFunction);


    This proxy generator can also be used with the Tide remoting API with the option tide="true". You then will be able to use typesafe service proxies either declared manually :

    Tide.getInstance().addComponent("personService", PersonService)

    PersonService(tideContext.personService).modifyPerson(person, modifyResult)

    Or much easier, using implicit declaration and injection :

    [In]
    public var personService:PersonService;

    personService.modifyPerson(person, modifyResult);


    Note that since the generator requires the annotation @RemoteDestination to identity service interface, it's better to use it than @TideEnabled if you want to use typesafe proxies, even if it requires an unused id attribute.

    The service generation can also be done with the Eclipse builder plugin, in this case just add the service interfaces classes in the 'Java Sources' section of the plugin properties page.




    Generation of flex-config.xml



    Another very frequent cause of errors with AMF objects serialization/deserialization comes from the Flex compiler not including all classes in the compiled SWF because they are not referenced anywhere in the code.
    The fix generally consists in adding dummy variables of the missing type, but this is very tedious and it is very easy to forget one.
    It would be a lot easier if the Flex compiler had an option to always include all classes in a source directory (don't hesitate to vote for the following feature request on the Adobe Flex JIRA) but as long as this is not the case, we felt the need to provide something similar ourselves.

    So there is a new option in the gas3 Eclipse builder plugin that automatically builds a flex-config.xml in the root folder of the project including all as3 files from the current source path.



    Then you just have to add the following compiler option :

    -load-config += flex-config.xml

    And you can say goodbye to problems with missing classes.


    Custom entity factory



    If you use the gas3 ant task, you maybe know that it's already possible to define custom templates for all kinds of generated elements and in particular for entity classes with attributes like entitytemplate="blah.gsp" and entitybasetemplate="blahbase.gsp".

    However your custom templates are still limited to use what is provided by the gas3 entity reflection factory. For example it's not easily possible to generate custom as3 annotation from existing Java annotations.
    To allow this, you can now define a custom implementation of EntityFactory that will build a custom instance of JavaEntityBean where you could parse and store whatever information you need from the Java class.

    For example you could build a HVEntityFactory that reads Hibernate Validator 3 annotations to generate custom constraint annotations in the as3 entity class. You could do this with :

    <gas3 entityfactory="com.myapp.MyEntityFactory"
    entitybasetemplate="file:///C:/myapp/myEntityBase.gsp"/>

    mercredi 4 novembre 2009

    New in GraniteDS 2.1.0 RC1: simplified configuration for Seam and Spring

    Hi all,

    Starting a new Flex + GraniteDS + Seam/Spring project or 'granitizing' an existing project is not a very complex task but involves quite a few configuration files and makes hard to get started for new users.

    Following the releases of the Spring-BlazeDS project and the beginning of the Seam-BlazeDS integration, we have implemented a similar mechanism that in most cases requires only one line of specific server configuration for GraniteDS.

    Let's start by Seam :

    The setup of a Seam + GraniteDS project used to require changes or additions of web.xml, components.xml, granite-config.xml and services-config.xml.

    With GDS 2.1, all you need is to add the GDS libraries in WEB-INF/lib or ear/lib and modify components.xml :

    <components xmlns="http://jboss.com/products/seam/components"
    xmlns:core="http://jboss.com/products/seam/core"
    xmlns:security="http://jboss.com/products/seam/security"
    xmlns:transaction="http://jboss.com/products/seam/transaction"
    xmlns:persistence="http://jboss.com/products/seam/persistence"
    xmlns:framework="http://jboss.com/products/seam/framework"
    xmlns:bpm="http://jboss.com/products/seam/bpm"
    xmlns:jms="http://jboss.com/products/seam/jms"
    xmlns:web="http://jboss.com/products/seam/web"
    xmlns:graniteds="http://www.graniteds.org/config"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
    "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.0.xsd
    http://jboss.com/products/seam/transaction http://jboss.com/products/seam/transaction-2.0.xsd
    http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.0.xsd
    http://jboss.com/products/seam/web http://jboss.com/products/seam/web-2.0.xsd
    http://jboss.com/products/seam/jms http://jboss.com/products/seam/jms-2.0.xsd
    http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.0.xsd
    http://jboss.com/products/seam/bpm http://jboss.com/products/seam/bpm-2.0.xsd
    http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd
    http://jboss.com/products/seam/framework http://jboss.com/products/seam/framework-2.0.xsd
    http://www.graniteds.org/config http://www.graniteds.org/config/granite-config-2.1.xsd">


    <core:init jndi-pattern="myapp/#{ejbName}/local" debug="true"/>

    <core:manager concurrent-request-timeout="500"
    conversation-timeout="120000" conversation-id-parameter="cid" parent-conversation-id-parameter="pid"/>

    <persistence:entity-manager-factory name="ejb3" persistence-unit-name="ejb3"/>

    <persistence:managed-persistence-context name="entityManager" entity-manager-factory="#{ejb3}"/>

    <security:identity jaas-config-name="other"/>


    <graniteds:flex-filter url-pattern="/graniteamf/*" tide="true"/>

    </components>

    The important part is the flex-filter declaration. It allows to map an url-pattern (in most cases /graniteamf/* is a good default value) to the GraniteDS AMF request processor. The attribute tide="true" defines a Tide/Seam service factory, otherwise this will be a simple Seam service factory.
    This configuration assumes that the Seam filter is mapped in web.xml to /*, which is the case for Seam projects generated by seam-gen or by the Eclipse Seam tools.

    Note that if you need to use Gravity, you still will have to declare manually the Gravity servlet in web.xml and change the filter mapping for SeamFilter. This is because the declaration depends on the servlet container and SeamFilter cannot be used with most container comet implementations (in particular Tomcat 6 CometProcessor).


    Now Spring :

    No big surprise, this is almost exactly the same for Spring, except of course that this time it assumes that a Spring MVC DispatcherServlet is mapped, so there is still some changes in web.xml if you don't have one :

    web.xml :
    <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/graniteamf/*</url-pattern>
    </servlet-mapping>

    applicationContext.xml :
    <beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:graniteds="http://www.graniteds.org/config"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
    http://www.graniteds.org/config http://www.graniteds.org/config/granite-config-2.1.xsd">

    <context:component-scan base-package="com.myapp.service" />

    <tx:annotation-driven transaction-manager="transactionManager"/>

    ...

    <graniteds:flex-filter url-pattern="/*" tide="true"/>

    </beans>

    The configuration is very similar to the one for Seam, except that the url-pattern is this time relative to the url-mapping of the Spring DispatcherServlet. This is also very similar to the configuration for Spring-BlazeDS.

    It is also possible to share an existing DispatcherServlet between a Web/Spring MVC and a GraniteDS/Flex front-ends, only the Flex channel endpoint url will be different (for example http://myserver/myapp/spring/graniteamf/amf instead of http://myserver/myapp/graniteamf/amf).


    In these two new configurations, there is no need for services-config.xml or granite-config.xml, however you can add one for more detailed configuration options.


    The absence of service-config.xml means that the RemoteObject channels can not be defined during the compilation of the Flex application. That means that it will be necessary to provide them manually :

    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    xmlns:cs="test.granite.components.*"
    layout="vertical"
    backgroundGradientColors="[#0e2e7d, #6479ab]"
    preinitialize="preinit()">

    <mx:Script>
    <![CDATA[
    private var graniteChannelSet:ChannelSet;

    private function preinit():void {
    Spring.getInstance().initApplication();

    graniteChannelSet = new ChannelSet();
    graniteChannelSet.addChannel(new AMFChannel("graniteamf", "http://{server.name}:{server.port}/myapp/graniteamf/amf"));

    Spring.getInstance().remoteObjectInitializer = function(ro:RemoteObject):void {
    ro.channelSet = graniteChannelSet;
    };

    }
    ]]>
    </mx:Script>
    ...
    </mx:Application>

    This a bit more complex that simply using services-config.xml, but that also means that you can dynamically configure the channel set, for example using the technique described here.


    This new Seam/Spring configuration can handle a few options that were only in granite-config.xml before :

    <graniteds:flex-filter url-pattern="/*" tide="true">
    <graniteds:tide-annotations>
    <graniteds:value>org.springframework.stereotype.Controller</graniteds:value>
    </graniteds:tide-annotations>
    <graniteds:exception-converters>
    <graniteds:value>com.myapp.util.MyExceptionConverter</graniteds:value>
    </graniteds:exception-converters>
    </graniteds:flex-filter>


    Messaging destinations that were previously declared in services-config.xml can now also be declared in components.xml or applicationContext.xml :

    Simple messaging destination :
    <graniteds:messaging-destination 
    name="addressBookTopicDestination"
    id="addressBookTopic"
    no-local="true"
    session-selector="true"/>


    JMS topic destination :
    <graniteds:jms-topic-destination id="addressBookTopic"
    name="dataTopic"
    connection-factory="ConnectionFactory"
    jndi-name="topic/testTopic"
    acknowledge-mode="AUTO_ACKNOWLEDGE"
    transacted-sessions="true"
    no-local="true"
    session-selector="true"/>



    Hopefully these new configuration options will make easier for new users to get started with GraniteDS and maybe for Spring-BlazeDS users to easily switch to GraniteDS if they want to.

    mardi 25 août 2009

    Build a Flex CRUD application in 15 minutes with the Grails GraniteDS plugin

    A few month ago, I've shown how to build a simple CRUD application with Flex, Grails and the GraniteDS plugin. That was not overly complex but there was still room for improvement in the quantity of code that was needed to make things work.
    Grails itself provides a feature called scaffolding that allows to build extremely easily a simple working application skeleton from constraints defined on the domain classes.

    Doing the same with Flex is a bit more complex because there are two runtimes involved: the Flash client and the Java server. There are thus three main possible options:

  • Generation and compilation of static Flex code on the server. Its advantages are static typing and full customizability. Its drawbacks are the quantity of code to maintain (even if it's generated, it's still here) and a relative difficulty to do incremental development (once the generated app has been customized, it's harder to update it when the source model changes). This is the option taken by the GFS plugin for example.

  • Dynamic generation of domain metadata on the server used by a Flex framework to build the UI dynamically. This has the big advantage of being completely driven by the server, so changes on the domain classes are immediately visible on the UI without requiring a recompilation of the client, and moreover there is relatively little client code. The corresponding drawback is that the generated application is not typed (client side data would be represented as XML for example) and is more difficult to maintain and customize.

  • Generation of a static Flex domain model and dynamic generation of the Flex UI. This is the approach taken by the GraniteDS plugin, its advantages are a limited amount of client code and a strongly typed client application. The drawbacks are that it's less dynamic than option 2 (changes on the Grails domain class require a recompilation of the Flex application), and less easily customizable than option 1.


  • After this short introduction, let's see how this works with the latest release of gdsflex (0.7.1).

    First we create a new Grails application, once again a book manager (maybe next time I'll find some more original example). Assuming you have grails 1.1.1 installed, type from the command line :

    grails create-app gdsexample2

    cd gdsexample2

    grails install-plugin gdsflex


    As before, the main part is to define the domain model. Let's start this time by a simple Author class.

    grails create-domain-class com.myapp.Author

    Author.groovy

    package com.myapp

    class Author {

    static constraints = {
    name(size:0..50, blank:false)
    birthDate(format:'DD/MM/YYYY')
    picture(nullable:true, widget:'image')
    }

    String uid

    String name

    Date birthDate

    java.sql.Blob picture
    }

    This is a relatively simple domain class, you can notice the constraints that drive Grails scaffolding. So let's add a simple controller :

    grails create-controller com.myapp.Author

    AuthorController.groovy

    package com.myapp

    @org.granite.tide.annotations.TideEnabled
    class AuthorController {

    def index = { redirect(action:list, params:params) }

    def scaffold = Author
    }

    Then run :

    grails generate-flex-app

    grails mxmlc

    grails run-app

    And go to http://localhost:8080/gdsexample2/gdsexample2.swf.

    You should have a basic working Flex application that allows to create and edit authors. So what happened here :

  • The gas3 generator has generated ActionScript 3 classes for each domain class (as that was the case before), this time incuding the existing Grails constraints.

  • A new scaffolding template for controllers has been installed in src/templates/scaffolding that includes the necessary actions for the Flex application.

  • The Flex UI builder classes (org.granite.tide.uibuilder) have been installed in grails-app/views/flex.

  • A main mxml has been generated in grails-app/views/flex/gdsexample2.mxml that includes a menu to access all domain classes.


  • This is a simple example, but we can see how the constraints have been used : the Grails constraints in Author.groovy have been translated to the following block in Author.as :

    public static const meta_constraints:Array = [
    { property: "name",
    blank: "false", size: "0..50"
    },
    { property: "birthDate",
    format: "DD/MM/YYYY"
    },
    { property: "picture",
    widget: "image"
    }
    ]

    Even if you don't want to use the GraniteDS UI builder framework, you could still use these as3 constraints from any custom framework.

    You can check that the blank and size constraints have been mapped to Flex client-side validators, and that the date format has been used in the author table. The 'image' widget is simply a component that appears in edit mode and allows to upload an image from Flex.

    Now that we have seen a basic domain class, let's see how it works with associations.

    grails create-domain-class com.myapp.Book

    grails create-domain-class com.myapp.Chapter

    grails create-controller com.myapp.Book

    Book.groovy

    package com.myapp

    class Book {

    static constraints = {
    title(size:0..100, blank:false)
    category(inList:["Fiction", "Non-fiction", "Biography"])
    author()
    description(size:0..2000, widget:"textArea")
    chapters()
    }

    String uid

    String title

    String category

    Author author

    Set chapters

    String description

    static hasMany = [ chapters:Chapter ]
    static mapping = {
    author fetch:"join"
    chapters cascade:"all-delete-orphan"
    }
    }

    Chapter.groovy

    package com.myapp

    class Chapter {

    static constraints = {
    title(size:0..50, blank:false)
    }

    String uid

    Book book

    String title

    String summary

    static mapping = {
    book fetch:"join"
    }
    }

    BookController.groovy

    package com.myapp

    @org.granite.tide.annotations.TideEnabled
    class BookController {

    def index = { redirect(action:list, params:params) }

    def scaffold = Book
    }

    An important thing is that ManyToOne associations must be marked fetch:"join" or lazy:false. GraniteDS does not support transparent lazy loading for single ended associations, and most likely never will (it would generate unacceptable network and database traffic, one http request and one SQL query for each uninitialized entity). Lazy collections are supported, so OneToMany and ManyToMany associations do not have to be marked lazy:false.

    Let's regenerate the Flex application to add the Book menu to the main mxml.

    grails generate-flex-app

    grails mxmlc


    You can notice that the main mxml has been overwritten. This is quite annoying if you made some changes to the generated file but the previous file is backuped and in general you would have added the entity manually in the mxml. If you have a look to the mxml, only two lines have been added for the new entity :

    <mx:LinkButton label="Books" width="100%" textAlign="left"
    click="mainStack.selectedChild = bookUI" />


    <ui:EntityUI id="bookUI"
    entityClass="{Book}"
    width="100%" height="100%"/>

    The EntityUI is a Flex component and can just be used anywhere in a Flex application (provided the Tide framework initialization has been done correctly).

    We can now create and edit books, and see how many-to-one and one-to-many associations are displayed in the generated application.

    As you eventually tried to add chapters, you will see that it does not work. There are indeed two minor changes to do manually in the AS3 classes to help the UI builder make the link between the associated oneToMany entities: initialize the Chapters collection in the Book.as class, and add a constructor in Chapter.as :

    public function Book():void {
    chapters = new ArrayCollection();
    }


    public function Chapter(book:Book = null):void {
    this.book = book;
    }

    Once these changes are made, just refresh the browser and check that you can add chapters to your books.

    Now we're almost finished, we'll just add a final improvement to this simple app:

    grails html-wrapper

    As the command name indicates, this will generate a html wrapper and allow the application to use Flex deep linking, and to use Grails url mapping on the flex application now located at flexindex.gsp.

    Now you can go to http://localhost:8080/gdsexample2/flexindex.gsp#book/list to access the book list directly.

    The documentation on the plugin page gives more information on what constraints are supported by the builder and how to override the behaviour of the generated application. Anyway the sources of the UI builder are also present in the grails-app/views/flex folder and can be used as a template to build your customized UI.

    It should be easy to add security and data push to the generated application by following the previous blog entry here.

    Hopefully this new feature of the gdsflex Grails plugin is a useful tool to get started quickly with Flex and GraniteDS and to quickly build prototype applications.
    As always, feedback and comments are welcome.

    mercredi 20 mai 2009

    What's New in GraniteDS 2.0.0 RC1


    Main New Features
    :

    Granite Data Services 2.0.0 Release Candidate 1 has just been released and comes with four main new features:
    1. Google App Engine support: GDS applications may now be deployed on Google's infrastructure. See details in William's related posts on this blog.
    2. Servlet 3.0 preview: Gravity now implements data push with Servlet 3.0 API and may be tested with Glassfish v3 preview (lastest promoted builds only). See below for details.
    3. Configuration MBeans: you may now use your JMX console in order to manage several GraniteDS options and watch for runtime configuration details. See below for details.
    4. JDO annotations support: Gas3 now generates suitable ActionScript3 code for JDO annotated entities and the DataNucleusExternalizer takes care of JDO's specific annotations.
    Granite-config.xml Changes (MUST READ):

    The granite-config.dtd has changed in order to make the config more readable: as a general rule, all configuration attributes use now a "dashed" convention: "annotedwith" is now spelled "annotated-with", "instanceof" "instance-of", etc. There is also a new "gravity" section whose meaning content is detailed in "Gravity Changes" below.

    The new DTD is online here and will help you with a DTD aware XML editor.

    Gravity Changes:

    Gravity code was getting messy, with several duplicated source sections, and hard to maintain. With the addition of Servlet 3.0 and GAE support, things were going even more harder, so the code has been cleaned up and widely refactored. However, the new code has been fully tested and shouldn't bring any problem.

    A new feature in this rewriting is the use of a thread pool for publishing messages. Previously, a published message was delivered to subscribed channels in a synchronous way, using the publish request container thread (delivering means only duplicating the message for all channels subscribed to the message's topic and adding each copy to each channel's queue). This operation was potentially time consuming with a large number of channels connected to the same topic and could block the caller container thread for a significant amount of time. A thread pool is now used for that purpose in all servlet implementations (Tomcat, JBossWeb, Jetty and Servlet 3.0) except for GAE.

    Instead of servlet parameters, Gravity configuration now goes in a dedicated section of granite-config.xml. Here is an example of all options with there default values:

    <gravity
    factory="org.granite.gravity.DefaultGravityFactory"
    channel-idle-timeout-millis="1800000"
    long-polling-timeout-millis="20000"
    reconnect-interval-millis="30000"
    reconnect-max-attempts="60">

    <thread-pool
    core-pool-size="5"
    maximum-pool-size="20"
    keep-alive-time-millis="10000"
    queue-capacity="2147483647" />

    </gravity>

    This section is purely optionnal and you may omit it if you accept default values. Full documentation will be provided later but here is some comments on the most important options:
    • channel-idle-timeout-millis: the elapsed time after which an idle channel (pure producer or dead client) may be silently unsubcribed and removed by Gravity. Default is 30 minutes.
    • long-polling-timeout-millis: the elapsed time after which an idle connect request is closed, asking the client to reconnect. Default is 20 seconds. Note that setting this value isn't supported in Tomcat/APR configurations.
    • thread-pool attributes: all options are standard parameters for the Gravity ThreadPoolExecutor instance.
    You may also use the new Gravity MBean in order to change most of these options at runtime (see below).

    Servlet v3 support is new in this RC1 and should be considered as beta software. The only up-to-date implementation of the new API at this time is in Glassfish v3 trunk and you may download it here (Servlet 3.0 support has been tested with glassfish-v3-preview-b47-windows.exe and may or may not work with other builds).

    Configuring the GDS chat sample for Glassfish v3 is a two steps process:
    1. Edit the graniteds/examples/env.properties and set SERVER_HOME to your glassfishv3/glassfish directory and SERVER_HOMER_DEPLOY to ${SERVER_HOME}/domains/domain1/autodeploy.
    2. Copy the content of the graniteds/examples/graniteds_chat/resources/WEB-INF/servlet3-web.xml into the web.xml file. Then, use the "deploy" target of chat build file and start Glassfish (see Glassfish documentation).
    You should then be able to connect to http://localhost:8080/graniteds-chat/ and test the application.

    Configuration MBeans:

    This RC1 comes with two new MBeans for GDS runtime management:
    • GraniteConfig MBean: this mbean will show an almost complete configuration state of GDS at runtime, with several things that were not accessible previously (except with a debugger): externalizers used so far, associated with serialized classes, enabled or disabled Tide components, etc. The only available operation provided by this MBean is a reload button: it will re-read your deployed granite-config.xml file and take care of your modifications. Note that everything may not be reconfigured at runtime but this may be at least a great tool for finding issues, for example, with externalizers configuration.
    • GravityConfig MBean: this mbean will display the current state of Gravity, letting you modify at runtime timeouts and pool sizing. You'll be also able to restart Gravity: note however that this is only an emergency feature because all undelivered queued messages will be lost!
    To access these MBeans with JBoss, browse to http://localhost:8080/jmx-console and scroll to the "org.granite" section.

    GraniteDS 2.0 now supports server push on Google App Engine

    We are close to releasing GraniteDS 2.0 RC1, hopefully the last release before GA. It includes a big refactoring of the Gravity core to simplify the implementation of new providers, and in particular there is a new Gravity provider for Google App Engine based on Memcache.

    An example of this Google App Engine push implementation is available at http://gdsgaepush.appspot.com, once again with admin/admin or user/user. If you open two browsers on the application, the changes made on from side should be also updated on the other. The eclipse project can be downloaded here.

    The GAE environment is very interesting because it is fully clustered: each request can be unpredictably handled by a different server instance. The only reliable way to communicate between different requests/users is by using the datastore or memcache (or a combination of both). In a first messaging implementation, we have chosen the memcache API that allows fast communication between processes and is easy to use. The main drawback is that it is not a reliable store and that client subscriptions or messages could be lost under high memory usage, but for non critical use it might be acceptable.

    The implementation is relatively simple: Gravity subscriptions and channels are stored in the cache with a configurable expiration delay. Each channel maintains its current number of pending messages in the cache. Publisher threads just increment this number and store new messages in the cache, and subscriber threads poll the channel counter at regular intervals and get existing messages from the cache when there are available. The poller thread by default polls the channel every 500ms and has a total duration of 20s (under the 30s limit of GAE).

    It's important to note that due to the use of memcache, only serializable objects can be sent in Gravity messages (but it's also the case with JMS). Besides that simple limitation, all features of Gravity (client <-> client, server -> client, selectors, ...) can be used with GAE (see documentation here).

    The configuration of the GAE provider can be done as follows :

  • Define the Gravity GAE servlet in WEB-INF/web.xml :
  • <servlet>
    <servlet-name>GravityServlet</servlet-name>
    <servlet-class>org.granite.gravity.gae.GravityGAEServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>GravityServlet</servlet-name>
    <url-pattern>/gravityamf/*</url-pattern>
    </servlet-mapping>

  • Setup the GAE Gravity factory in WEB-INF/granite/granite-config.xml :
  • <gravity 
    factory="org.granite.gravity.gae.GAEGravityFactory
    channel-idle-timeout-millis="1800000">
    <configuration>
    <gae polling-interval="500">
    </configuration>
    </gravity>

  • Finally define the GAE service adapter in WEB-INF/flex/services-config.xml :
  • <service id="gravity-service"
    class="flex.messaging.services.MessagingService"
    messageTypes="flex.messaging.messages.AsyncMessage">
    <adapters>
    <adapter-definition id="gae" class="org.granite.gravity.gae.GAEServiceAdapter"/>
    </adapters>

    <destination id="exampleDestination">
    <channels>
    <channel ref="my-gravityamf"/>
    </channels>
    <adapter ref="gae"/>
    </destination>
    </services>

    This new server push provider complements the current existing support for AMF remoting and JDO persistence and makes GraniteDS 2.0 a complete platform for deploying Flex applications on Google App Engine.

    As always, feedback and comments are welcome.