William | Granite Data Services

Cocomonio


Archive for William:

Thoughts about JavaFX and Bean Validation

By William November 29th, 2012 GraniteDS, JavaFX 1 Comment

Coming from Adobe Flex, it has been quite a surprise to discover that JavaFX 2 does not provide anything particular concerning input validation. However we can make use of the existing UI event model to execute validations on the text inputs.

In a first naive implementation, we could simply think of an event handler that will manually validate the input on the fly :

textField.setOnKeyTyped(new EventHandler<KeyEvent>() {
    @Override
    public void handle(KeyEvent event) {
        String text = ((TextInputControl)event.getTarget()).getText());
        if (text.length() < 2 || text.length() > 25)
            ((Node)event.getTarget()).setStyle("-fx-border-color: red");
        else
            ((Node)event.getTarget()).setStyle("-fx-border-color: null");
    }
});

Here we have processed the validation manually, but we now would like to be able to leverage the Bean Validation API to simplify this. Bean Validation is not meant to be applied on individual values (as the value of the input) but on data beans holding the validation constraint annotations. However in general a text field would be used to populate a data bean, so we could do something like this :

public class Person {

    @Size(min=2, max=20)
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

Having defined this bean, we could change the previous code by this :

final Validator validator = validatorFactory.getValidator(Person.class);

textField.setOnKeyTyped(new EventHandler<KeyEvent>() {
    @Override
    public void handle(KeyEvent event) {
        String text = ((TextInputControl)event.getTarget()).getText());
        Set<ConstraintViolation<Person>> violations =
            validator.validateValue(Person.class, "name", text);
        if (!violations.isEmpty())
            ((Node)event.getTarget()).setStyle("-fx-border-color: red");
        else
            ((Node)event.getTarget()).setStyle("-fx-border-color: null");
    }
});

This is definetely better, but we still have to do this for each and every input field, which is not very convenient.

One possibility would be to write a reusable validation handler, like this :

public class ValidationEventHandler<T> extends EventHandler<KeyEvent> {

    private Class<T> beanClass;
    private String propertyName;

    public ValidationEventHandler(Class<T> beanClass, String propertyName) {
        this.beanClass = beanClass;
        this.propertyName = propertyName;
    }

    @Override
    public void handle(KeyEvent event) {
        String text = ((TextInputControl)event.getTarget()).getText());
        Set<ConstraintViolation<T>> violations =
            validator.validateValue(beanClass, propertyName, text);
        if (!violations.isEmpty())
            ((Node)event.getTarget()).setStyle("-fx-border-color: red");
        else
            ((Node)event.getTarget()).setStyle("-fx-border-color: null");
    }
}

Which would lead to this on each field :

textField.setOnKeyTyped(
    new ValidationEventHandler<Person>(Person.class, "name"));

Still we have to define this handler on each input field, so it can be configured with the bean class and property.

If we want to go a bit further, we will need a generic way to determine the links between input fields and beans. This link is application-specific, but if the application uses data binding, we can maybe find this information.

textField.text().bindBidirectional(person.name());

Unfortunately, there does not seem to be any generic reflection API on property bindings. Using the following extremely ugly hack we can find the target of a particular binding:

private Property<?> lookupBindingTarget(Property<?> inputProperty) {
    try {
        Field fh = inputProperty.getClass().getDeclaredField("helper");
        fh.setAccessible(true);
        Object helper = fh.get(inputProperty);
        Field fcl = helper.getClass().getDeclaredField("changeListeners");
        fcl.setAccessible(true);
        Object changeListeners = fcl.get(helper);
        if (changeListeners != null && Array.getLength(changeListeners) > 0) {
            ChangeListener<?> cl =
                (ChangeListener<?>)Array.get(changeListeners, 0);
            try {
                Field fpr = cl.getClass().getDeclaredField("propertyRef2");
                fpr.setAccessible(true);
                WeakReference<?> ref= (WeakReference<?>)fpr.get(cl);
                Property<?> p = (Property<?>)ref.get();
                return p;
            }
            catch (NoSuchFieldException e) {
                 log.debug("Field propertyRef2 not found on " + cl, e);
                 return null;
            }
        }
        log.debug("Could not find target for property %s", inputProperty);
	return null;
    }
    catch (Exception e) {
        log.warn(e, "Could not find target for property %s", inputProperty);
        return null;
    }
}

Using this hack, We are now able to determine automatically the beans to validate for all input fields in a form. This is exactly what the FormValidator class provided by GraniteDS is built to do. It scans all inputs in a JavaFX container and add listeners on them. It then calls the corresponding validator on the target bean and propagates the constraint violations to the input by dispatching a particular event of type ValidationResultEvent.

Using FormValidator with data binding, you would simply have to do this:

FormValidator formValidator = new FormValidator();
formValidator.setForm(formContainer);

Then all bound input fields will be validated on-the-fly depending on the Bean Validation constraints defined on the target beans. The FormValidator dispatches validation events on the container so you can react and display hints and error messages to the user:

formContainer.addEventHandler(ValidationResultEvent.ANY,
    new EventHandler<ValidationResultEvent>() {
    @Override
    public void handle(ValidationResultEvent event) {
        if (event.getEventType() == ValidationResultEvent.INVALID)
            ((Node)event.getTarget()).setStyle("-fx-border-color: red");
        else if (event.getEventType() == ValidationResultEvent.VALID)
            ((Node)event.getTarget()).setStyle("-fx-border-color: null");
    }
});

Note that for now, the FormValidator requires that the data bean implements the GraniteDS interface DataNotifier which basically means that the bean is able to dispatch JavaFX events. This requirement could be possibly removed in a future release.

public interface DataNotifier extends EventTarget {
    public <T extends Event> void addEventHandler(EventType<T> type,
        EventHandler<? super T> handler);
    public <T extends Event> void removeEventHandler(EventType<T> type,
        EventHandler<? super T> handler);
}

The bean would then be implemented like this :

public class Person {

    private EventHandlerManager __handlerManager
        = new EventHandlerManager(this); 

    @Override
    public EventDispatchChain buildEventDispatchChain(EventDispatchChain t) {
        return t.prepend(__handlerManager);
    }

    public <T extends Event> void addEventHandler(EventType<T> type,
        EventHandler<? super T> handler) {
        __handlerManager.addEventHandler(type, handler);
    }
    public <T extends Event> void removeEventHandler(EventType<T> type,
        EventHandler<? super T> handler) {
        __handlerManager.removeEventHandler(type, handler);
    }
}

This is once again not ideal but this can be easily extracted in an abstract class.

The validation step of the FormValidator is as follows:

Set<ConstraintViolation<Object>> allViolations
    = validatorFactory.getValidator().validate((Object)entity, groups);

Map<Object, Set<ConstraintViolation<?>>> violationsMap
    = new HashMap<Object, Set<ConstraintViolation<?>>>();
for (ConstraintViolation<Object> violation : allViolations) {
    Object rootBean = violation.getRootBean();
    Object leafBean = violation.getLeafBean();
    Object bean = leafBean != null
        && leafBean instanceof DataNotifier ? leafBean : rootBean;

    Set<ConstraintViolation<?>> violations = violationsMap.get(bean);
    if (violations == null) {
        violations = new HashSet<ConstraintViolation<?>>();
        violationsMap.put(bean, violations);
    }
    violations.add(violation);
}

for (Object bean : violationsMap.keySet()) {
    if (bean instanceof DataNotifier) {
        ConstraintViolationEvent event =
            new ConstraintViolationEvent(
                ConstraintViolationEvent.CONSTRAINT_VIOLATION,
                violationsMap.get(bean)
            );
        Event.fireEvent((DataNotifier)bean, event);
    }
}

Obviously that would be easier if we could plug in the bean validation lifecycle of each bean and fire the events during the validation itself, itself of doing this kind of postprocessing.

Conclusion

We have already most of the building blocks to integrate JavaFX and Bean Validation but it is definitely not as smooth as it should be.

The main pain points are the following :

  1. No way to plug in the validation lifecycle of Bean Validation
  2. No easy-to-use JavaFX UI components to display error popups
  3. No reflection API on the JavaFX data binding
  4. No simple event dispatch facility for beans

In fact, the two last points could be totally unneccessary if the validation was directly built-in the data binding feature, much like converters. It is probably possible to build something like a ValidatableStringProperty, we will let this for a future post.

Granite Data Services 3.0.0.M1 is out

By William November 26th, 2012 Annoucements, GraniteDS No Comments
Please, feature an image for this post.

Granite Data Services 3.0.0.M1 is out and available for download . Maven artifacts are also available through Maven2 central repositories .

This release is the first milestone for GraniteDS 3.0 and comes with several bug fixes, improvements, and new features (see the complete changelog on JIRA ).

Here are some highlights of the most interesting new features in GraniteDS 3.0.0.M1 :

  • Apache Flex(R) 4.8.0 compatibility (see here)
  • New messaging transport based on websockets on application servers supporting websockets : Tomcat 7.0.29+, Jetty 8.1+, GlassFish 3.1.2+.
  • Improved implementation of the Java client libraries (new remoting API, support for long polling and websocket messaging
  • Initial implementation of client libraries for JavaFX 2.2 : data management, client integration with Spring
  • Full support for Spring Data JPA, ability to generate AS3 / JavaFX beans for AbstractPersistable, and service proxies for Repositories.

Note : the Flex websocket implementation is a modified version of the work of gimite available on github . It will be packaged in a separate archive in the next 3.0 M2 release.

You can have a look at the reference documentation for Flex or Java/JavaFX to get more details on the configuration for using websockets. It is very similar to the usual Gravity long polling setup and simply involves declaring a WebSocketChannel and a corresponding WebSocket servlet.

The packaging of the distribution has been completely changed. You will now get a cleaner (and smaller) archive containing 5 folders:

  • libraries: server jars, Flex and Java client libraries and dependencies
  • tools: libraries for Gas3 and Gfx Ant tasks (generators of AS3 and JavaFX beans)
  • docs: JavaDoc and ASDoc API documentation for Flex and Java/JavaFX, and reference guides for Flex and JavaFX
  • sources: source jars suitable for attachment in Eclipse
  • samples: zip of sample projects that can be directly imported in Eclipse

Notes on upgrading from 2.x to 3.0 :

  • You must now use the 3.0 xsd for Seam and Spring XML configurations : http://www.graniteds.org/config http://www.graniteds.org/public/dtd/3.0.0/granite-config-3.0.xsd
  • The flex-filter element must be replaced by server-filter
  • When using Servlet 3 annotation configuration, @FlexFilter must be replaced by @ServerFilter

You can find the reference documentation here :

  • Flex:
  • Java/JavaFX:

To get started with the new support of JavaFX 2.2, you can follow the new tutorials :

The latest Maven archetypes 2.0.0.M1 have also been upgraded to GraniteDS 3.0.0.M1 and a new JavaFX/Spring archetype has been added.

You can note that the source projects are not (and won’t be) in the distribution any more. You can still clone them directly from github:

  • Core Framework: git://github.com/graniteds/graniteds.git
  • Java Client: git://github.com/graniteds/graniteds_java_client.git
  • Eclipse plugin: git://github.com/graniteds/graniteds_builder.git

The core project still includes the old examples (in the examples folder) that have not yet been ported to the new format.

Starting from GDS 3.0, support for JDK 1.4 is dropped. If you’re stuck on JDK 1.4, you can still use the JDK 1.4 build of GraniteDS 2.3.2.GA.

The Eclipse tooling of GDS 3.0 (Gas3 builder and wizard) can be updated from our Eclipse .

Data Management Tutorial with JavaFX 2.2

By William November 26th, 2012 GraniteDS, JavaFX 1 Comment

Following the release of GraniteDS for JavaFX, here is the version of the data management tutorial for this new view technology. What we call data management is a set of features that allow to work easily with data objects on the client and simplify the boilerplate due to client/server integration.

The example project is hosted on GitHub at and requires Maven 3.x for building. It is also required to use a JDK 1.7.0_07 or better so JavaFX is already installed on the Java runtime. You may have to change your JAVA_HOME environment variable if you have many JDK installed. It may be useful to follow the tutorial that you have an Eclipse installation, ideally with the M2E plugin installed (to automatically update Maven dependencies). Spring Tool Suite is a good choice, all the more that we are going to use the Spring framework on both the server and the client.

For the impatient, you can simply clone the project and build it. From a console, type the following:

git clone git://github.com/graniteds/shop-admin-javafx.git
cd shop-admin-javafx
mvn install

To start the embedded Jetty server, type this:

cd webapp
mvn jetty:run

To start the JavaFX client application, open a second console and type the following:

cd shop-admin-javafx/javafx
java -jar target/shop-admin-javafx.jar

When the application shows up, just logon as admin/admin. It’s a simple CRUD example which allows searching, creating and modifying vineyards and the wines they produce.

The application is definitely ugly but its goal is simply to demonstrate the following features :

  • Basic CRUD with a Spring Data JPA repository
  • Support for lazy-loading of JPA x-to-many associations
  • Dirty-checking / Undo
  • Client validation with Bean Validation API
  • Security
  • Real-time push

For the sake of simplicity, this tutorial has a few known limitations:

  • The search is case-sensitive
  • Validation errors are displayed as simple red borders without the corresponding message
  • The ‘Save’ button is incorrectly enabled when something is typed in the search input

Each step corresponds to a tag on the GitHub project so you can see what has been changed at each step.

So now let’s start from scratch :

Step 1 : Create the Project from the Maven Archetype

If you have cloned the project from GitHub, just do this:

git checkout step1

This first step has been simply created by issuing the following command :

mvn archetype:generate
-DarchetypeGroupId=org.graniteds.archetypes
-DarchetypeArtifactId=org.graniteds-tide-spring-jpa
-DarchetypeVersion=2.0.0.M1
-DgroupId=com.wineshop
-DartifactId=shop-admin-javafx
-Dversion=1.0-SNAPSHOT
-Dpackage=com.wineshop

If you look at the result, the archetype has created a Maven project with three modules :

  • A Spring server module
  • A Webapp module
  • A JavaFX client module

The Spring server module includes a suitable persistence.xml JPA descriptor and a basic domain model and service (Welcome + WelcomeService).

The Webapp module includes the necessary Spring configuration for JPA with a simple HQL datasource, Spring Security and GraniteDS. It also includes a web.xml configured with a Spring dispatcher servlet, and Gravity Comet and WebSocket configurations for Jetty 8. The pom.xml of this module also includes the necessary configuration to run an embedded Jetty 8.

Finally the JavaFX module includes a pom.xml with the configuration to generate JavaFX client model from the JPA model with the GraniteDS gfx ant task, and to package the application as an executable jar. It also includes a skeleton JavaFX client application with the necessary Spring container and GraniteDS configurations.

The generated application is basically a slighly improved Hello World where the names that are sent are stored in the database and pushed to all clients. You can check the push by running many JavaFX clients simultaneously.

To start the embedded Jetty server, type this:

cd webapp
mvn jetty:run

To start a JavaFX client application, open another console and type the following:

cd shop-admin-javafx/javafx
java -jar target/shop-admin-javafx.jar

You can log in with admin/admin or user/user.

Before continuing, we will just remove some of this initially generated stuff from the project and eclipsify it. The tag step1b contains a cleaned up version of the project that you can import in Eclipse (actually you will get 4 projects).

git checkout step1b

Step 2 : Implement Basic CRUD Functionality

This will be the longest step as we are creating most of the application.

The Server Application

First we have to build the server application. For convenience we are going to use the new support for Spring Data JPA repositories, and simply define a JPA model.

com/wineshop/entities/Vineyard.java:

@Entity
public class Vineyard extends AbstractEntity {

    private static final long serialVersionUID = 1L;

    @Basic
    private String name;

    @Embedded
    private Address address = new Address();

    @OneToMany(cascade=CascadeType.ALL, mappedBy="vineyard",
        orphanRemoval=true)
    private Set wines;

    public String getName() {
        return name;
    }

    public void setName(String nom) {
        this.name = nom;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Set getWines() {
        return wines;
    }

    public void setWines(Set wines) {
        this.wines = wines;
    }
}

com/wineshop/entities/Wine.java:

@Entity
public class Wine extends AbstractEntity {

    private static final long serialVersionUID = 1L;

    public static enum Type {
        RED,
        WHITE,
        ROSE
    }

    @ManyToOne
    private Vineyard vineyard;

    @Basic
    private String name;

    @Basic
    private Integer year;

    @Enumerated(EnumType.STRING)
    private Type type;

    public Vineyard getVineyard() {
        return vineyard;
    }

    public void setVineyard(Vineyard vineyard) {
        this.vineyard = vineyard;
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer annee) {
        this.year = annee;
    }

    public String getName() {
        return name;
    }

    public void setName(String nom) {
        this.name = nom;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }
}

com/wineshop/entities/Address.java:

@Embeddable
public class Address implements Serializable {

    private static final long serialVersionUID = 1L;

    @Basic
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String adresse) {
        this.address = adresse;
    }
}

Note that our entities extend the AbstractEntity that is provided by the archetype. AbstractEntity simply has a Long id, a Long version field and a uid field. We will mostly replace it by AbstractPersistable from Spring Data but we have to keep it because of the uid property. The uid field is a global persistent identifier that is to be unique among all client and server layers, and is thus persisted in the database, but is not necessarily a database key. The GraniteDS data management framework can work without a specific uid field but with some restrictions (see the documentation for further explanation).

Next we are going to define the Spring Data repository, but first we have to add Spring Data in our dependencies in java/pom.xml.

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-jpa</artifactId>
  <version>1.2.0.RELEASE</version>
<dependency>

We change our AbstractEntity to extends AbstractPersistable (not necessary useful but more Spring Data-esque):

@MappedSuperclass
@EntityListeners({AbstractEntity.AbstractEntityListener.class,
    DataPublishListener.class})
public abstract class AbstractEntity extends AbstractPersistable {

    private static final long serialVersionUID = 1L;

    /* "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 Integer getVersion() {
        return version;
    }

    @Override
    public boolean equals(Object o) {
        return (o == this || (o 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;
    }
}

And define the repository interface :

com/wineshop/services/VineyardRepository.java

@RemoteDestination
@DataEnabled
public interface VineyardRepository
    extends FilterableJpaRepository {
}

As you can see, this repository extends the GraniteDS-specific FilterableJpaRepository which is an extension of the default Spring JpaRepository that adds an extra finder method, findByFilter. This findByFilter is a kind of find by example implementation. We might possibly consider contributing this code to Spring Data JPA later to avoid this dependency on a GraniteDS implementation (or ditch it completely if Spring Data comes with something similar in a future release).

The @RemoteDestination annotation indicates that the repository is remote-enabled, that is will be accessible from our GraniteDS client. In general this is not what you would do for obvious security reasons (and for example create a Service in front of the repository) but we want to keep it simple here. The @DataEnabled annotation indicates that GraniteDS should track JPA data updates happening during the execution of the service methods and propagate them to the client.

Finally we register our repository in webapp/src/main/webapp/WEB-INF/spring/app-jpa-config.xml:

<jpa:repositories
    base-package="com.wineshop.services"
    factory-class="org.granite.tide.spring.data.FilterableJpaRepositoryFactoryBean"/>

There is a tag step2a on the git project so you can see what has been changed since step 1.

git checkout step2a

Here the compare view on GitHub:

Now you can rebuild and restart the Jetty server:

mvn install
cd webapp
mvn jetty:run

You may have noticed that the gfx generator is ran as part of the maven build. If you have a look at the JavaFX module, you can see some newly generated classes for the client entities and a client proxy for the Spring Data repository, in the packages com.wineshop.client.entities and com.wineshop.client.services. That will be useful for the next part, that is developing the client.

The JavaFX Client

Now the most interesting part, the JavaFX client. To simplify things, we are going to keep some elements of the skeleton application, the Main and the Login screen. We are mostly going to work on the Home.fxml screen and Home.java controller.

Our UI will be very basic, a table view to display the list of vineyards, and a form to create or modify them.

The Table View

First we add the table. Here is the relevant part of Home.fxml:

<!-- Search Bar -->
<HBox spacing="10">
    <children>
        <TextField fx:id="fieldSearch" prefColumnCount="20"
            onAction="#search"/>
        <Button text="Search" onAction="#search"/>
    </children>
</HBox>

<TableView fx:id="tableVineyards" layoutX="10" layoutY="40"
    items="$vineyards">
    <columns>
        <TableColumn fx:id="columnName" text="Name"
            prefWidth="320" sortable="true">
            <cellValueFactory>
                <PropertyValueFactory property="name"/>
            </cellValueFactory>
        </TableColumn>
    </columns>
</TableView>

 

Nothing very fancy, we simply define a table view control with a single column mapped to the name property of our Vineyard entity, and a search bar with a seach field and a search button. The data source for the table view is defined as $vineyards, that would usually mean that we have to bind it to a collection in the controller. If you look at the Main class coming from the archetype, it uses the custom TideFXMLLoader that automatically exposes all beans in the Spring context as FXML variables. So we create a Spring bean named vineyards in the application configuration in Main.java:

@Bean
public PagedQuery vineyards(ServerSession serverSession)
    throws Exception {
    PagedQuery vineyards =
        new PagedQuery(serverSession);
    vineyards.setMethodName("findByFilter");
    vineyards.setMaxResults(25);
    vineyards.setRemoteComponentClass(VineyardRepository.class);
    vineyards.setElementClass(Vineyard.class);
    vineyards.setFilterClass(Vineyard.class);
    return vineyards;
}

We use the GraniteDS PagedQuery component which wires a client observable list (itself) to a server finder method, here the findByFilter method of the Spring Data repository. This component also handles paging automatically so we can define a maxResults property which defines the maximum number of elements that will be retrieved from the server at each remote call. PagedQuery also handles remote filtering and sorting, so we next have to wire it to the view in the Home.java controller initialization:

@Inject
private PagedQuery vineyards;

The PagedQuery is a Spring bean, the Home controller too, so we can inject one into the other as needed.

@Override
public void initialize(URL url, ResourceBundle rb) {
    vineyards.getFilter().nameProperty()
        .bindBidirectional(fieldSearch.textProperty());
    vineyards
        .setSort(new TableViewSort(tableVineyards, Vineyard.class));
    ...
}

These declarations bind the filter name property to the search field and define the sort adapter between the TableView control and the PagedQuery component.

Finally we define the search action on the controller, it just has to call the method refresh on the PagedQuery component that will trigger a remote call to get an up-to-date dataset:

@FXML
private void search(ActionEvent event) {
    vineyards.refresh();
}

With this quite simple setup, we have a fully functional table view on our remote Vineyard entity.

The Edit Form

Here is the form description in Home.fxml:

<Label fx:id="labelFormVineyard" text="Create vineyard"/>
<GridPane fx:id="formVineyard" hgap="4" vgap="4">
    <children>
        <Label text="Name"
            GridPane.columnIndex="1" GridPane.rowIndex="1"/>
        <TextField fx:id="fieldName"
            GridPane.columnIndex="2" GridPane.rowIndex="1"/>

        <Label text="Address"
            GridPane.columnIndex="1" GridPane.rowIndex="2"/>
        <TextField fx:id="fieldAddress"
            GridPane.columnIndex="2" GridPane.rowIndex="2"/>
    </children>
</GridPane>
<!-- Button Bar -->
<HBox spacing="10">
    <children>
        <Button fx:id="buttonSave" text="Save"
            onAction="#save"/>
        <Button fx:id="buttonDelete" text="Delete"
            onAction="#delete" visible="false"/>
        <Button fx:id="buttonCancel" text="Cancel"
            onAction="#cancel" visible="false"/>
    </children>
</HBox>

 

Once again, nothing very spectacular. We now have to wire it to the controller, so we first add an instance variable vineyard that will hold the currently edited entity.

@FXML
private Vineyard vineyard;

Not very useful by itself, we now have to manage this variable correctly. We will do this in a method that will bind the form to an existing instance or create a new instance:

private void select(Vineyard vineyard) {
    if (vineyard == this.vineyard && this.vineyard != null)
        return;

    if (this.vineyard != null) {
        fieldName.textProperty()
            .unbindBidirectional(this.vineyard.nameProperty());
        fieldAddress.textProperty()
            .unbindBidirectional(this.vineyard.getAddress().addressProperty());
    }

    if (vineyard != null)
        this.vineyard = vineyard;
    else {
        this.vineyard = new Vineyard();
        this.vineyard.setName("");
        this.vineyard.setAddress(new Address());
        this.vineyard.getAddress().setAddress("");
    }

    fieldName.textProperty()
        .bindBidirectional(this.vineyard.nameProperty());
    fieldAddress.textProperty()
        .bindBidirectional(this.vineyard.getAddress().addressProperty());

    labelFormVineyard.setText(vineyard != null
        ? "Edit vineyard" : "Create vineyard");
    buttonDelete.setVisible(vineyard != null);
    buttonCancel.setVisible(vineyard != null);
}

Additionally this method changes the title of the form to ‘edit’ or ‘create’ and makes the delete and cancel buttons visible when working on an existing instance. We will just call this method at the initialization of the screen and define a selection listener on the table to bind the selection to the form:

public void initialize(URL url, ResourceBundle rb) {
    ...

    select(null);
    tableVineyards.getSelectionModel().selectedItemProperty()
        .addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue<!--? extends Vineyard--> property,
            Vineyard oldSelection, Vineyard newSelection) {
            select(newSelection);
        }
    });
}

We’re almost done, finally we have to define the actions of the three buttons:

@FXML
private void save(ActionEvent event) {
    final boolean isNew = vineyard.getId() == null;
    vineyardRepository.save(vineyard,
        new SimpleTideResponder() {
            @Override
            public void result(TideResultEvent tre) {
                if (isNew)
                    select(null);
                else
                    tableVineyards.getSelectionModel()
                        .clearSelection();
            }

            @Override
            public void fault(TideFaultEvent tfe) {
                System.out.println("Error: "
                    + tfe.getFault().getFaultDescription());
            }
        }
    );
}

Basically we save the entity by calling the remote Spring Data repository that we got injected by Spring. Note that a suitable client has been generated for the repository and is defined as a Spring bean. On successful return, we either create a new empty entity with select(null) or simply clear the table selection, which will consequently clear the form and reset it in creation mode.

The delete action is quite similar:

@FXML
private void delete(ActionEvent event) {
    vineyardRepository.delete(vineyard.getId(),
        new SimpleTideResponder() {
            @Override
            public void result(TideResultEvent tre) {
                tableVineyards.getSelectionModel().clearSelection();
            }
        }
    );
}

The cancel operation is very basic for now:

@FXML
private void cancel(ActionEvent event) {
    tableVineyards.getSelectionModel().clearSelection();
}

You can certainly notice that we call the remote repository and don’t care about the actual result of the operation. In fact we don’t have because GraniteDS listens to the JPA events and propagates them to the client as Spring application events. The PagedQuery automatically listens to these client events and refreshes itself when needed. Of course if you need to access the result objects, you can still do it in the result handler.

The first step of the client application is now ready. You can get it with the tag step2 in the git repository:

git checkout step2

Here the compare view on GitHub:

You can now build it and run it, assuming your Jetty server it still running in another console:

cd javafx
mvn clean install
java -jar target/shop-admin-javafx.jar

Step 3: Support for JPA lazy associations

If you are still here, you have maybe noticed that we simply didn’t take care of the wines association. It is never populated, saved or rendered and that caused no problem to the application. GraniteDS is indeed able to properly serialize and deserialize all lazy association so you simply don’t have to care about them. What is lazy on the server stays lazy on the client.

Now we would like to edit the list of wines for our vineyards. We first add a list view to the edit form:

<Label text="Wines" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<HBox spacing="5" GridPane.columnIndex="2" GridPane.rowIndex="3">
    <children>
        <ListView fx:id="listWines" maxHeight="150"/>

        <VBox spacing="5">
            <children>
                <Button text="+" onAction="#addWine"/>
                <Button text="-" onAction="#removeWine"/>
            </children>
        </VBox>
    </children>
</HBox>

 

Now in the controller, we have to bind the list of wines of the current edited vineyard to this list:

@FXML
private ListView listWines;
private void select(Vineyard vineyard) {
    ...
    listWines.setItems(this.vineyard.getWines());
    ...
}

And add the actions to add and remove a wine from the list:

@FXML
private void addWine(ActionEvent event) {
    Wine wine = new Wine();
    wine.setVineyard(this.vineyard);
    wine.setName("");
    wine.setYear(Calendar.getInstance().get(Calendar.YEAR)-3);
    wine.setType(Wine$Type.RED);
    this.vineyard.getWines().add(wine);
}

@FXML
private void removeWine(ActionEvent event) {
    if (!listWines.getSelectionModel().isEmpty())
        this.vineyard.getWines().remove(listWines.getSelectionModel().getSelectedIndex());
}

Finally we have to setup the list to display and edit the properties of the Wine objects:

listWines.setCellFactory(new Callback, ListCell>() {
    public ListCell call(ListView listView) {
        return new WineListCell();
    }
});
private static class WineListCell extends ListCell {

    private ChoiceTypeListener choiceTypeListener = null;

    protected void updateItem(Wine wine, boolean empty) {
        Wine oldWine = getItem();
        if (oldWine != null && wine == null) {
            HBox hbox = (HBox)getGraphic();

            TextField fieldName = (TextField)hbox.getChildren().get(0);
            fieldName.textProperty()
                .unbindBidirectional(getItem().nameProperty());

            TextField fieldYear = (TextField)hbox.getChildren().get(1);
            fieldYear.textProperty()
                .unbindBidirectional(getItem().yearProperty());

            getItem().typeProperty().unbind();
            getItem().typeProperty().removeListener(choiceTypeListener);
            choiceTypeListener = null;

            setGraphic(null);
        }

        super.updateItem(wine, empty);

        if (wine != null && wine != oldWine) {
            TextField fieldName = new TextField();
            fieldName.textProperty()
                .bindBidirectional(wine.nameProperty());

            TextField fieldYear = new TextField();
            fieldYear.setPrefWidth(40);
            fieldYear.textProperty()
                .bindBidirectional(wine.yearProperty(), new IntegerStringConverter());

            ChoiceBox choiceType = new ChoiceBox(
                FXCollections.observableArrayList(Wine$Type.values())
            );
            choiceType.getSelectionModel()
                .select(getItem().getType());
            getItem().typeProperty()
                .bind(choiceType.getSelectionModel().selectedItemProperty());
            choiceTypeListener = new ChoiceTypeListener(choiceType);
            getItem().typeProperty()
                .addListener(choiceTypeListener);

            HBox hbox = new HBox();
            hbox.setSpacing(5.0);
            hbox.getChildren().add(fieldName);
            hbox.getChildren().add(fieldYear);
            hbox.getChildren().add(choiceType);
            setGraphic(hbox);
        }
    }

    private final static class ChoiceTypeListener
        implements ChangeListener {

        private ChoiceBox choiceBox;

        public ChoiceTypeListener(ChoiceBox choiceBox) {
            this.choiceBox = choiceBox;
        }

        @Override
        public void changed(ObservableValue<!--? extends Wine$Type--> property,
                Wine$Type oldValue, Wine$Type newValue) {
            choiceBox.getSelectionModel().select(newValue);
        }
    }
}

Ouch! This cell implementation looks intimidating but in fact we simply create 3 text and choice fields for the values we want to edit in the Wine object. Then we set bidirectional binding between each field and the corresponding property of the Wine class. ChoiceBox is the most complex because we can’t bind from the selectedItem property (?), so we have to define a change listener to achieve the same result.

There is nothing else to change, this is purely client code. The persistence will be ensured by the cascading options we have defined on the JPA entity. Interestingly we don’t have to handle the loading of the collection, Tide will trigger a remote loading of the collection content when the content is first requested, for example when a UI control tried to display the data.

As before, build and run:

git checkout step3

Compare view on GitHub:

cd javafx
mvn clean install
java -jar target/shop-admin-javafx.jar

Step 4: Dirty Checking / Undo

If you have played with the application you may have noticed that using bidirectional bindings leads to a strange behaviour. Even without saving your changes, the local objects are still modified and keep the modifications made by the user. To fix this, we can use the fact that GraniteDS tracks all updates made on the managed entities and is able to easily restore the last known stable state of the objects (usually the last fetch from the server).

We need to inject the local entity manager:

@Inject
private EntityManager entityManager;

And use it to restore the persistent state of the object when the user selects another element without saving:

private void select(Vineyard vineyard) {
    if (vineyard == this.vineyard && this.vineyard != null)
        return;

    if (this.vineyard != null) {
        fieldName.textProperty().unbindBidirectional(this.vineyard.nameProperty());
        fieldAddress.textProperty().unbindBidirectional(this.vineyard.getAddress().addressProperty());
        entityManager.resetEntity(this.vineyard);
    }
    ...
}

We can also enable or disable the ‘Save’ button depending on the fact that the user has modified something or not. Tide provides the DataManager

@Inject
private JavaFXDataManager dataManager;
buttonSave.disableProperty()
    .bind(Bindings.not(dataManager.dirtyProperty()));

If you try this, you will notice that it works fine when modifying existing data but not with newly created elements. This is because these new elements are not known by the entity manager, and thus not tracked by the dirty checking process. To make this work, we have to merge the new entities in the entity manager:

else {
    this.vineyard = new Vineyard();
    this.vineyard.setName("");
    this.vineyard.setAddress(new Address());
    this.vineyard.getAddress().setAddress("");
    entityManager.mergeExternalData(this.vineyard);
}

As before, there is a tag step4 on the git repository.

git checkout step4

Compare view on GitHub:

cd javafx
mvn clean install
java -jar target/shop-admin-javafx.jar

Validation

We can create, edit and search in our database. We would now like to ensure that our data in consistent. The Bean Validation API is our friend and we can use it on both the server JPA entities and on the client data objects. Going back to the JPA model, we add a few validation annotations, here the Wine class:

@Basic
@Size(min=5, max=100,
    message="The name must contain between {min} and {max} characters")
private String name;

@Basic
@Min(value=1900,
    message="The year must be greater than {value}")
@Past
private Integer year;

@Enumerated(EnumType.STRING)
@NotNull
private Type type;

By adding we ensure that we cannot save incorrect values. However we would also like to notify the user that something went wrong. The brutal way would be to add a special handling of validation error in each and every fault handler of the application. A better way would be to define a global exception handler that will handle all validation faults. Indeed Tide already provides such a thing, and it takes server exceptions and propagates them as events on the faulty property of the target data object. Finally we would have to listen to these events and display some message or trigger some notification to the user. GraniteDS provides a special component, the FormValidator, that will further simplify our work. We will simply have to attach it to the form containing the fields that we want to validate after the entity to validate has been bound:

private FormValidator formValidator = new FormValidator();

...

private void select(Vineyard vineyard) {
    if (vineyard == this.vineyard && this.vineyard != null)
        return;

    formValidator.setForm(null);

    ...

    formValidator.setForm(formVineyard);

    ...
}

Finally we have to define a UI behaviour when a validation event occurs, for example setting a red border on the faulty fields:

formVineyard.addEventHandler(ValidationResultEvent.ANY, new EventHandler() {
    @Override
    public void handle(ValidationResultEvent event) {
        if (event.getEventType() == ValidationResultEvent.INVALID)
            ((Node)event.getTarget()).setStyle("-fx-border-color: red");
        else if (event.getEventType() == ValidationResultEvent.VALID)
            ((Node)event.getTarget()).setStyle("-fx-border-color: null");
    }
});

You could do whatever you want in this handler and apply a more suitable display, for example display the error message.

If you test the application now, that should work fine, but the user is still able to submit the save button even with invalid data. It’s easy to block the remote call:

@FXML
private void save(ActionEvent event) {
    if (!formValidator.validate(this.vineyard))
        return;

    ...
}

Tag step5 on the git repository.

git checkout step5

Compare view on GitHub:

cd javafx
mvn clean install
java -jar target/shop-admin-javafx.jar

Step 6: Security

The application already has a basic security with the login page. If you look how this works, you will find the component Identity which is a gateway between the client and the Spring Security framework.

Just as an exercise, we can add a logout button to our application:

<Button text="Logout" onAction="identity.logout(null)"/>

 

With a tiny bit of JavaScript, we can call the logout method of identity. As we have defined a change listener on the property loggedIn of identity in the Main class, the current view will be destroyed and replaced by the login screen.

We can also decide in the initialization of the Home controller that only administrators can delete entities:

buttonDelete.disableProperty().bind(Bindings.not(identity.ifAllGranted("ROLE_ADMIN")));

Tag step6 on the git repository.

git checkout step6

Compare view on GitHub:

cd javafx
mvn clean install
java -jar target/shop-admin-javafx.jar

Step 7: Real-time data push

Until now, we have used only one client at a time. We are going to configure GraniteDS to push JPA data updates from the server to all connected clients. We have almost already everything in place, the archetype has setup a complete configuration with Jetty 8 websockets. When deploying on another container, you might need to change the configuration to use the specific websocket support of Tomcat 7+ or GlassFish 3.1.2+, or fallback to simple long-polling with the portable Servlet 3 implementation.

First we need to declare a messaging destination in the server configuration app-config.xml:

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

Declare the topic and enable automatic publishing on the Spring Data repository @DataEnabled annotation:

@RemoteDestination
@DataEnabled(topic="wineshopTopic", publish=PublishMode.ON_SUCCESS)
public interface VineyardRepository extends FilterableJpaRepository {
}

Declare a client DataObserver in the Spring configuration and subscribe this topic when the user logs in:

@Bean(initMethod="start", destroyMethod="stop")
public DataObserver wineshopTopic(ServerSession serverSession,
    EntityManager entityManager) {
    return new DataObserver("wineshopTopic", serverSession, entityManager);
}

We listen to the LOGIN and LOGOUT events in the Login controller to subscribe and unsubscribe the topic:

if (ServerSession.LOGIN.equals(event.getType())) {
    wineshopTopic.subscribe();
}
else if (ServerSession.LOGOUT.equals(event.getType())) {
    wineshopTopic.unsubscribe();
}
...

Now you can build the project and run two or more instances of the application in different consoles. Changes made on a client should be propagated to all other subscribed clients.

Tag step7 on the git repository.

git checkout step7

Compare view on GitHub:

cd javafx
mvn clean install
java -jar target/shop-admin-javafx.jar

Conclusion

This tutorial is now finished. There are still a few more interesting features to show such as conflict detection and resolution but the goal of this tutorial is to show the iterations needed to build a full featured JavaFX application with the help of the GraniteDS JavaFX integration. JavaFX is still a moving target and some parts of this tutorial might be simplified with future releases, notably as the support for expression bindings in FXML improves.

GraniteDS Data Management Tutorial

By William December 5th, 2011 Flex, GraniteDS 5 Comments
Please, feature an image for this post.

We have setup a simple example project that demonstrates most features of GraniteDS when used in a Spring/Hibernate technology stack.

The project is available on GitHub at and requires Maven 3.x for building.

Just issue the following commands to try it :

git clone git://github.com/wdrai/wineshop-admin.git
cd wineshop-admin
mvn clean install
cd webapp
mvn jetty:run-war

Now you can browse . You can log in with admin/admin or user/user.

It’s a simple CRUD example which allows searching, creating and modifying vineyards and the vines they produce. The application is definitely ugly but its goal is simply to demonstrate the following features :

  • Basic CRUD with a Spring service
  • Support for lazy-loading of JPA x-to-many associations
  • Dirty-checking
  • Client validation
  • Real-time push
  • Reverse lazy-loading

Each step corresponds to a tag on the GitHub project so you can see what has been changed at each step.

Let’s rebuild this project from scratch.

Step 1 : Create the project with the Maven archetype

This is the easiest one :

mvn archetype:generate<br />
    -DarchetypeGroupId=org.graniteds.archetypes<br />
    -DarchetypeArtifactId=org.graniteds-tide-spring-jpa<br />
    -DarchetypeVersion=1.1.0.GA<br />
    -DgroupId=com.wineshop<br />
    -DartifactId=wineshop-admin<br />
    -Dversion=1.0-SNAPSHOT<br />

Then check that the initial project is working :

cd wineshop-admin<br />
mvn clean install<br />
cd webapp<br />
mvn jetty:run-war<br />

And browse . You will get the default hello world application.

Step 2 : Implement basic CRUD functionality

This is the longer step as we are going to build most of the application : the JPA entity model, the Spring service and a basic Flex client. Here is the entity model, there is nothing special here.

@Entity
public class Vineyard extends AbstractEntity {
    private static final long serialVersionUID = 1L;
    @Basic
    private String name;
    @OneToMany(cascade=CascadeType.ALL, mappedBy="vineyard",
        orphanRemoval=true)
    private Set<Wine> wines;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;<br />
    }
    public Set<Wine> getWines() {
        return wines;<br />
    }
    public void setWines(Set<Wine> wines) {
        this.wines = wines;
    }
}
@Entity
public class Wine extends AbstractEntity {

    private static final long serialVersionUID = 1L;

    public static enum Type {
        RED,
        WHITE,
        ROSE
    }

    @ManyToOne
    private Vineyard vineyard;

    @Basic
    private String name;

    @Basic
    private Integer year;

    @Enumerated(EnumType.STRING)
    private Type type;

    public Vineyard getVineyard() {
        return vineyard;
    }
    public void setVineyard(Vineyard vineyard) {
        this.vineyard = vineyard;<br />
    }
    public Integer getYear() {
        return year;<br />
    }
    public void setYear(Integer annee) {
        this.year = annee;<br />
    }
    public String getName() {
        return name;<br />
    }
    public void setName(String nom) {
        this.name = nom;
    }
    public Type getType() {
        return type;
    }
    public void setType(Type type) {
        this.type = type;
    }
}

The Spring service interface to handle this model :

@RemoteDestination
@DataEnabled(topic="")
public interface WineshopService {
    public void save(Vineyard vineyard);
    public void remove(Long vineyardId);
    public Map<String, Object> list(Vineyard filter,
        int first, int max, String[] sort, boolean[] asc);
}

As you can see, there are two specific annotations on the service. @RemoteDestination indicates that the service is exposed to the Flex application, and that an ActionScript3 proxy will be generated for the service. @DataEnabled indicates that GraniteDS will track the JPA updates on the entities and report them automatically to the relevant clients.

Then the implementation :

@Service
public class WineshopServiceImpl implements WineshopService {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void save(Vineyard vineyard) {
        entityManager.merge(vineyard);
        entityManager.flush();
    }

    @Transactional
    public void remove(Long vineyardId) {
    	Vineyard vineyard = entityManager.find(Vineyard.class, vineyardId);
    	entityManager.remove(vineyard);
        entityManager.flush();
    }

    @Transactional(readOnly=true)
    public Map<String, Object> list(Vineyard filter,
            int first, int max, String[] sort, boolean[] asc) {

    	StringBuilder sb = new StringBuilder("from Vineyard vy ");
    	if (filter.getName() != null)
    		sb.append("where vy.name like '%' || :name || '%'");
    	if (sort != null && sort.length > 0) {
    		sb.append("order by ");
    	        for (int i = 0; i < sort.length; i++)
    		      sb.append(sort[i]).append(" ").append(asc[i] ? " asc" : " desc");
        }
    	Query qcount = entityManager.createQuery("select count(vy) "
             + sb.toString());
    	Query qlist = entityManager.createQuery("select vy "
             + sb.toString()).setFirstResult(first).setMaxResults(max);
    	if (filter.getName() != null) {
    		qcount.setParameter("name", filter.getName());
    		qlist.setParameter("name", filter.getName());
    	}
    	Map<String, Object> result = new HashMap<String, Object>(4);
    	result.put("resultCount", (Long)qcount.getSingleResult());
    	result.put("resultList", qlist.getResultList());
    	result.put("firstResult", first);
    	result.put("maxResults", max);
    	return result;
    }
}

This is a classic Spring JPA service. There are two particularities however :

  • It uses merge to update the entities. This is important as objects that are transferred between Flex and Java are considered as detached objects.
  • The method list has the specific signature (filter, first, max, sort[], asc[]) so it can be easily used from the GraniteDS paged collection implementation.

Note however that there is no particular dependency on GraniteDS, this service can be used by any other client.

Finally the Flex client application in Home.mxml :

<?xml version="1.0" encoding="utf-8"?>

<s:VGroup
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:e="com.wineshop.entities.*"
    xmlns="*"
    width="100%" height="100%">

    <fx:Metadata>[Name]</fx:Metadata>

    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;

            import org.granite.tide.spring.Spring;
            import org.granite.tide.collections.PagedQuery;
            import org.granite.tide.events.TideResultEvent;
            import org.granite.tide.events.TideFaultEvent;

            import com.wineshop.entities.Vineyard;
            import com.wineshop.entities.Wine;
            import com.wineshop.entities.Wine$Type;
            import com.wineshop.services.WineshopService;

            Spring.getInstance().addComponentWithFactory("vineyards", PagedQuery,
                { filterClass: Vineyard, elementClass: Vineyard, remoteComponentClass: WineshopService, methodName: "list", maxResults: 12 }
            );

            [In] [Bindable]
            public var vineyards:PagedQuery;

            [Inject]
            public var wineshopService:WineshopService;

            private function save():void {
                 wineshopService.save(vineyard);
            }

            private function remove():void {
                wineshopService.remove(vineyard.id, function(event:TideResultEvent):void {
                    selectVineyard(null);
                });
            }

            private function selectVineyard(vineyard:Vineyard):void {
                this.vineyard = vineyard;
                vineyardsList.selectedItem = vineyard;
            }
        ]]>
    </fx:Script>

	<fx:Declarations>
		<e:Vineyard id="vineyard"/>
	</fx:Declarations>

	<s:VGroup paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10" width="800">
		<s:HGroup id="filter">
			<s:TextInput id="filterName" text="@{vineyards.filter.name}"/>
			<s:Button id="search" label="Search" click="vineyards.refresh()"/>
		</s:HGroup>

		<s:List id="vineyardsList" labelField="name" width="100%" height="200"
				change="selectVineyard(vineyardsList.selectedItem)">
			<s:dataProvider><s:AsyncListView list="{vineyards}"/></s:dataProvider>
		</s:List>

		<s:Button id="newVineyard" label="New" click="selectVineyard(new Vineyard())"/>
	</s:VGroup>

	<s:VGroup paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10" width="800">
		<mx:Form id="formVineyard">
			<mx:FormHeading label="{isNaN(vineyard.id) ? 'Create vineyard' : 'Edit vineyard'}"/>
			<mx:FormItem label="Name">
				<s:Label text="{vineyard.id}"/>
				<s:TextInput id="formName" text="@{vineyard.name}"/>
			</mx:FormItem>
			<mx:FormItem>
				<s:HGroup>
					<s:Button id="saveVineyard" label="Save"
							  click="save()"/>
					<s:Button id="removeVineyard" label="Remove"
							  enabled="{!isNaN(vineyard.id)}" click="remove()"/>
				</s:HGroup>
			</mx:FormItem>
		</mx:Form>
	</s:VGroup>

</s:VGroup>

This is no major complexity here, but there are some things that can be noted :

  • It uses the PagedQuery component to display the list of existing vineyards. The configuration section at the beginning (addComponentWithFactory…) links the client component to the service method we have define in our Spring service, so each time the component needs to fetch data, it will call the remote service transparently. This component also handles paging, so it will fetch next elements on demand when there are lots of data on the server and the user scrolls the list. This is also why it is wrapped in an AsyncListView collection view.
  • The PagedQuery also handles transparently filtering and sorting. Here we simply bind a TextInput to the filter object that is passed to the server method. When the user clicks on ‘Search’, we simply have to refresh the collection, as we would have done for a client filter. Sorting is even easier and is handled completely transparently when the collection is bound to a UI component that allows sorting, for example DataGrid.
  • The remote service invocation is completely typesafe thanks to the ActionScript 3 generator and the [Inject] annotation. If you refactor the service, you will instantly detect the inconsistency between the client and the server at build time. You can also benefit from code completion in the IDE.
  • The CRUD operations are completely ‘fire and forget’. You just call the server method, and GraniteDS will automatically handle the updates, you don’t have to do anything yourself. There is not even a result handler and the server method do not return anything. In fact GraniteDS listens to all JPA events though the DataPublishListener in AbstractEntity and dispatches them transparently to the Flex clients, including the client from which the call originates. The local caching does everything else and GraniteDS is able to match and merge the result with the initial object on the client. The remove operation also works transparently because the PagedQuery is able to handle the remove events and update itself. It we were using simple collections, we would have to handle manually the persist and remove events.

Now you can build the application with mvn clean install, restart jetty and check your changes.

Step 3 : Support for JPA lazy associations

There is nothing much to do, simply add a form item allowing to edit the list of wines for the selected vineyard. We can for example use a list with an item renderer containing editors for the properties of the Wine entity :

<s:FormItem label="Wines">
    <s:HGroup gap="10">
        <s:List id="formWines" dataProvider="{vineyard.wines}">
            <s:itemRenderer>
                <fx:Component>
                    <s:ItemRenderer>
                        <s:states><s:State name="normal"/></s:states>
                        <s:HGroup id="wineEdit">
                            <s:TextInput text="@{data.name}"/>
                            <s:TextInput text="@{data.year}"/>
                            <s:DropDownList
                                selectedItem="@{data.type}"
                                requireSelection="true"
                                dataProvider="{outerDocument.wineTypes}"
                                labelField="name"/>
                        </s:HGroup>
                    </s:ItemRenderer>
                </fx:Component>
            </s:itemRenderer>
        </s:List>				

        <s:VGroup gap="10">
            <s:Button label="+"
                click="vineyard.wines.addItem(new Wine(vineyard))"/>
            <s:Button label="-"
                 enabled="{Boolean(formWines.selectedItem)}"
                 click="vineyard.wines.removeItemAt(formWines.selectedIndex)"/>
        </s:VGroup>
    </s:HGroup>
</s:FormItem>

We just miss two minor things : add an argument to the constructor of Wine to be able to associate it to a Vineyard (here used for the add operation) :

public function Wine(vineyard:Vineyard = null):void {
    this.vineyard = vineyard;
}

And initialize the collection of wines for a new vineyard :

public function Vineyard():void {
    this.wines = new ArrayCollection();
}

Again, build the application with mvn install, restart jetty and check your changes.

As you can see, this is purely client code. We rely on cascading to persist the changes in the database, and GraniteDS is able to cleanly transfer lazy associations without much hassle.

More, when entities are fetched in the list of vineyards, their collections of wines are still not loaded. When the user selects a vineyard, the binding {vineyard.wines} on the list automatically triggers the loading of the collection from the server. This is completely transparent so you don’t even have to think about it !!

Step 4 : Dirty checking / Undo

If you have played with the application you may have noticed that using bidirectional bindings leads to strange behaviour. Even without saving your changes, the local objects are still modified. GraniteDS tracks all updates made on the managed entities and is able to easily restore the last known stable state of the objects (usually the last fetch from the server).

It’s also easily possible to enable or disable the ‘Save’ button depending on the fact that the user has modified something or not.

To achieve this, we have to ensure that the entity bound to the form is managed by GraniteDS (in particular for newly created entities because entities retrieved from the server are always managed). We have just to add a few lines when the user selects another element in the main list to restore the state of the previously edited element :

import org.granite.tide.spring.Context;

[Inject] [Bindable]
public var tideContext:Context;

private function selectVineyard(vineyard:Vineyard):void {
    Managed.resetEntity(this.vineyard);
    tideContext.vineyard = this.vineyard = vineyard;
    vineyardsList.selectedItem = vineyard;
}

Then we can use the meta_dirty property of the Tide context to enable/disable the ‘Save’ button :

<s:Button id="saveVineyard" label="Save"
     enabled="{tideContext.meta_dirty}" click="save()"/>

mvn install, jetty, …

Step 5 : Validation

Great, we can now create, edit and search in our database. Now we would like to ensure that the data is consistent. Instead of manually defining Flex validators on each field, we are going to use the Bean Validation API on the server and its GraniteDS implementation on the client.

First let’s add a few Bean Validation annotations on the model :

@Basic
@Size(min=5, max=100,
    message="The name must be between {min} and {max} characters")
private String name;

@Basic
@Min(value=1900,
    message="The year must be greater than {value}")
@Max(value=2050,
    message="The year must be less than {value}")
private Integer year;

@Enumerated(EnumType.STRING)
@NotNull
private Type type;
@Basic
@Size(min=5, max=100,
    message="The name must be between {min} and {max} characters")
private String name;

@OneToMany(cascade=CascadeType.ALL,
    mappedBy="vineyard", orphanRemoval=true)
@Valid
private Set<Wine> wines;

This will at least ensure that we cannot save invalid entities. However we would like that our user is informed that the operation has failed. One ugly way would be to add a fault handler on the save operation call with an alert. Instead we are simply going to use the FormValidator component that will validate the entity locally and interpret server exceptions to propagate the error messages to the correct input field.

First you have to register the validation exception handler that will process the validation errors coming from the server. This is not required in this example because all the constraints can be processed locally on the client, but it’s always useful in case the server has additional constraints. Just add this in the init method of Main.mxml.

Spring.getInstance().addExceptionHandler(ValidatorExceptionHandler);

In Home.mxml, add the v namespace and define a FormValidation attached to the edit form and the bound entity :

<s:VGroup
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:v="org.granite.validation.*"
    xmlns:e="com.wineshop.entities.*"
    xmlns="*"
    width="100%" height="100%"
    initialize="selectVineyard(new Vineyard())">
    ...
<fx:Declarations>
    <e:Vineyard id="vineyard"/>
    <s:ArrayCollection id="wineTypes" source="{Wine$Type.constants}"/>
    <v:FormValidator id="formValidator"
        entity="{vineyard}"
        form="{formVineyard}"/>
</fx:Declarations>

We can also define a FormValidator for the item renderer :

<s:itemRenderer>
    <fx:Component>
        <s:ItemRenderer>
            <fx:Declarations>
                <v:FormValidator id="wineValidator"
                    form="{wineEdit}" entity="{data}"/>
            </fx:Declarations>

            <s:states><s:State name="normal"/></s:states>
            <s:HGroup id="wineEdit">
                <s:TextInput text="@{data.name}"/>
                <s:TextInput text="@{data.year}"/>
                <s:DropDownList
                    selectedItem="@{data.type}"
                    requireSelection="true"
                    dataProvider="{outerDocument.wineTypes}"
                    labelField="name"/>
            </s:HGroup>
        </s:ItemRenderer>
    </fx:Component>
</s:itemRenderer>

These two declarations will allow to display error messages on the field during editing. The FormValidator takes advantage of the replication of the Bean Validation annotations to ActionScript 3 to know what validations have to be applied on the client data.

Finally we can keep the user from trying to save an invalid object by adding the following line :

private function save():void {
	if (formValidator.validateEntity())
		wineshopService.save(vineyard);
}

mvn install, jetty, …

Step 6 : Real-time data push

Enabling data push is just a question of configuration. There are 4 things to check :

  • Declare a topic messaging destination in the Spring configuration app-config.xml
  • <graniteds:messaging-destination id="wineshopTopic"
        no-local="true" session-selector="true"/>
    
  • Declare the topic and publish mode on the @DataEnabled annotation on the exposed services
  • @RemoteDestination
    @DataEnabled(topic="wineshopTopic", publish=PublishMode.ON_SUCCESS)
    public interface WineshopService {
    
  • Declare the DataObserver for this topic in Main.mxml
  • Spring.getInstance().addComponent("wineshopTopic", DataObserver);
    Spring.getInstance().addEventObserver("org.granite.tide.login",
        "wineshopTopic", "subscribe");
    Spring.getInstance().addEventObserver("org.granite.tide.logout",
        "wineshopTopic", "unsubscribe");
    

Of course the three declarations should use the same topic name, but this is all you need to enable data push.

mvn install, jetty, …

Now if you open many browsers, all the changes made in one browser should be dispatched to all other browsers.

Step 7 : Conflict handling

In any multiuser application, there may be cases where different users do changes on the same entity concurrently. Using optimistic locking is a common technique to handle these cases and avoid database inconsistencies. GraniteDS is able to handle these errors cleanly in either normal remoting operations or with real-time data push.

This is quite simple to configure, you just need to register an exception handler for the JPA OptimistickLockException and an event listener on the Tide context that will be called when a concurrent modification conflict occurs :

private function init():void {
    ...
    Spring.getInstance().addExceptionHandler(OptimisticLockExceptionHandler);

    Spring.getInstance().getSpringContext().addEventListener(
        TideDataConflictsEvent.DATA_CONFLICTS, conflictsHandler);
}

private function conflictsHandler(event:TideDataConflictsEvent):void {
    Alert.show("Someone has modified this vineyard at the same time\n. "
        + "Keep your changes ?",
        "Conflict", Alert.YES | Alert.NO, null, function(ce:CloseEvent):void {
        if (ce.detail == Alert.YES)
            event.conflicts.acceptAllClient();
        else
            event.conflicts.acceptAllServer();
    });
}

The most difficult part is to actually obtain a conflict. After your rebuild and restart jetty, open two browsers. Create a vineyard in one of the browsers, it will appear in the second one. Edit it in the second browser and change something, for example its name, without saving. Then in the first browser, change the name to a different value and save. An alert should appear in the second browser.

Step 8 : Reverse lazy loading

This final feature is not very visual, but it can improve a lot the performance of your application. The support for server-to-client lazy loading ensures that the amount of data transferred in this direction is limited, but a problem can arise in the other direction (client-to-server). Once all your object graph is loaded on the client by transparent loading or manual operations, the complete loaded object graph will be sent back to the server even when only a property of the root object has been changed. With very deep and complex object graphs, this can really kill the performance of write operations.

GraniteDS now provides a new feature called reverse lazy loading that allows to fold the object graph before transmitting it to the server. It will take in account the changes made locally by the user to fold all parts of the graph that have not been updated, and still send the parts of the graph containing the changes.

This can be setup as follows : in the initialization method of the application, register an argument preprocessor :

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

And then in the service methods that update entities, simply add the @Lazy annotation to the incoming arguments :

public void save(@Lazy Vineyard vineyard);

To really see what happens, you have to run mvn jetty:run-war in debug mode from the Eclipse M2E plugin, and put a breakpoint in the method save(). Then create a vineyard and some wines, and save it. Close and reopen the browser to restart from a clean state, edit and change the name of the vineyard you just created and click on Save. By inspecting the incoming Vineyard object on the debugger, you can see that the collection of wines is marked uninitialized. Now change the name of one of the wine and click on Save. This time the collection will be initialized and contain the Wine object you just updated.

Or you can just trust that it works and that it’s better to use it than not…

Conclusion

We’re done with this tutorial on the data management features. You are now be able to see what you can do with GraniteDS and how it can simplify your developments, and even bring new possibilities for your applications.

Quick start with GraniteDS

By William December 2nd, 2011 Flex, GraniteDS 14 Comments
Please, feature an image for this post.

You have now two very easy ways to quickly create a new GraniteDS project – or to get quickly started with the platform if you are not familiar with it: a dedicated Eclipse wizard and four Maven archetypes. Both allow to create fully functional projects with only few clicks or command-line options, resulting in pre-configured, ready to deploy, skeleton applications with most of the major features you would use in a real-world project: Flash Builder configuration, Ant build file, JavaEE framework and JPA provider integration, code generation setup, real-time messaging configuration, etc.

If you are a Maven user, you can start with the archetypes that work with embedded servers ( Jetty or Embedded GlassFish), see this paragraph below. Using the wizard can however be helpful in defining proper configurations for other target application servers and environments such as Tomcat or JBoss.

Using the Eclipse Wizard

First, you need to install the GraniteDS wizard and builder plugins in Eclipse. From Eclipse, you can use the Eclipse Marketplace dialog (search for “GraniteDS�?) or add the GraniteDS update site () to the list of available software sites:

You will need to install both the GraniteDS Builder and the GraniteDS Wizard plugins. It will register a GraniteDS / GraniteDS Project wizard with three default templates:

  • The Pojo template is a simple template that creates a project configured for basic remoting to a Java service.
  • The Chat template is a simple template that creates a project configured for basic client-to-client messaging with a default chat application.
  • The Spring/EJB/Seam/CDI template is a bit more advanced and allows to create a project configured with a complete Flex, Java and GraniteDS technology stack, including popular Java frameworks such as Spring and EJB3, JPA support and real-time data push.

The three templates generate a combined Flex/Java project that can be easily converted to an Eclipse WTP project and deployed to a local or remote application server. If you use Flash Builder, the necessary configuration files can be optionally generated so the project can be immediately compiled in Flash Builder. Finally a minimal ant build file will be produced so you can build the project manually if needed.

Let’s see how this works in a few steps:

First select the menu File / New / Other.. (or just type Ctrl+N) then lookup the GraniteDS section and select GraniteDS Project.

Select a template, for example the last one and click Next.

Choose a name for your project, choose your preferred technologies, for example choose Spring 3, Tomcat 7 and Hibernate. Fill up the other information, in particular your Flex SDK home directory (it should be ideally a Flex 4.5 SDK) and the deployment folder of the application server (for example something like /home/dev/apache-tomcat-7.0.22/webapps for Tomcat 7). Keep default values for the others, check Flash Builder and Ant+Ivy build options and click on Finish.

The creation of the new project in the workspace can take some time because the wizard will fetch all necessary libraries on the central Maven repository. If you are using Flash Builder 4.5, you may get the following warning because the generated configuration files are targeted at Flash Builder 4, so just select Flex SDK 4.5+.

With Flash Builder, you will always have an error about HTML wrapper files after the project is built (this is a known Flash Builder issue). Just right click on the error message as suggested and select Recreate HTML templates.

If you don’t use Flash Builder, you can simply use the target build.flex of the generated build.xml ant file that will run the compilation of the Flex application.

At this point, you already have a working project, fully configured for the technologies and server you have chosen in the wizard page. You have now two options to deploy it to your server: run the deploy.war target of the generated ant build.xml file or use Eclipse WTP.

In order to use WTP, you first have to convert the project to a faceted project by right clicking on the project and selecting the menu Configure / Convert to Faceted Form…

On the next page, select Dynamic Web Project with the correct version (3.0 for Tomcat 7 or JBoss 6/7, 2.5 for Tomcat 6 or JBoss 4/5) and select a corresponding server runtime.

Finally, right click on the project and select Debug / Debug on Server…

On the last screen, just check that the correct server is selected (here it should be Tomcat 7) and click on Finish.

Eclipse will start the application server and open a Web browser on the application welcome page. You should get something like this:

You can log in with admin:admin or user:user, and try to enter a few names. If you open a second browser (and not only another tab or window of the same browser!) and point it to the same page (), you should see your modifications reflected in real-time in both browsers.

If you setup automatic publishing in Eclipse WTP (that should be the case by default), any change you make on the Flex application will be automatically deployed on the server. You can simply refresh the page to check your changes once compiled, no need to redeploy anything to develop your UI!

Using the Maven archetypes

If you are a Maven user, it’s likely that you will prefer to start with an archetype. There are four existing GraniteDS archetypes available on the Maven central repository:

archetypeGroupId: org.graniteds.archetypes archetypeVersion: 1.1.0.GA archetypeArtifactId:

  • graniteds-spring-jpa-hibernate: Flex 4.5 + Spring 3 + Hibernate 3.6 + GraniteDS 2.3 with standard RemoteObject API
  • graniteds-tide-spring-jpa-hibernate: Flex 4.5 + Spring 3 + Hibernate 3.6 + GraniteDS 2.3 with Tide API
  • graniteds-tide-seam-jpa-hibernate: Flex 4.5 + Seam 2.2 + Hibernate 3.6 + GraniteDS 2.3 with Tide API
  • graniteds-tide-cdi-jpa: Flex 4.5 + CDI/Weld 1.1 + JPA 2.0 + GraniteDS 2.3 with Tide API

The Tide archetypes are equivalent to what you get with the Spring/EJB/Seam/CDI template of the Eclipse Wizard we have seen if the first paragraph. The main differences are that you don’t need to have a Flex SDK installed as it will be retrieved from the Maven repository and that you get 3 separate projects: a Java project, a Flex project and a Webapp project.

Let’s reproduce what we did with the Eclipse Wizard, first with a command line (Maven 3.x required):

mvn archetype:generate
    -DarchetypeGroupId=org.graniteds.archetypes
    -DarchetypeArtifactId=graniteds-tide-spring-jpa-hibernate
    -DarchetypeVersion=1.1.0.GA
    -DgroupId=org.example
    -DartifactId=springgds
    -Dversion=1.0-SNAPSHOT

Once the archetype is created, you can build the project with:

cd springgds
mvn clean package

And finally run the embedded jetty server with:

cd webapp
mvn jetty:run-war

You can now browse  and check that the application works.

The CDI archetype requires a Java EE 6 server and uses an embedded GlassFish that you can run with:

cd webapp
mvn embedded-glassfish:run

With the Eclipse Maven integration (the M2E plugin), you can simply choose one of the archetypes when doing New Maven Project.

To deploy the application to a real server, you can use the following goal to build a war file:

mvn war:war

Note however that when doing this you may have to change the configuration of the application. In general you have to change the name of the Gravity servlet in web.xml and most likely update your JPA configuration. You can use the Eclipse Wizard to generate a configuration corresponding to your case.

Conclusion

It now takes literally 5 minutes (and less than 1 minute after the first run) to start a new Flex / Java project with GraniteDS. You no longer have any excuse not to try it!

Maven archetypes updated to GraniteDS 2.3.0, Flex 4.5 and Flexmojos 4

By William November 21st, 2011 Annoucements, Flex, GraniteDS No Comments
Please, feature an image for this post.

Following the release of GraniteDS 2.3.0.GA, we have updated the Maven archetypes, now in version 1.1.0.GA.

Here are the changes :

  • Upgrade to GDS 2.3.0.GA
  • Upgrade to Flexmojos 4.0-RC2
  • Upgrade to Flex SDK 4.5.1
  • Fixed some issues in POMs when used in Eclipse with the m2e plugin
  • Fixed Tide/Seam archetype

As a reminder, here is how you can use the archetype to build a simple project with Flex/GraniteDS/Spring and Hibernate :

mvn archetype:generate 
  -DarchetypeGroupId=org.graniteds.archetypes 
  -DarchetypeArtifactId=graniteds-tide-spring-jpa-hibernate 
  -DarchetypeVersion=1.1.0.GA 
  -DgroupId=com.myapp 
  -DartifactId=example
  -Dversion=1.0-SNAPSHOT

There are still 4 existing archetypes, all of which are now based on Flex 4.5 and Spark components :

  • graniteds-spring-jpa-hibernate: Spring 3 + JPA/Hibernate + GraniteDS with RemoteObject API
  • graniteds-tide-spring-jpa-hibernate: Spring 3 + JPA/Hibernate + GraniteDS with Tide API
  • graniteds-tide-seam-jpa-hibernate: Seam 2.2 + JPA/Hibernate + GraniteDS with Tide API
  • graniteds-tide-cdi-jpa: CDI/Weld 1.1 + JPA + GraniteDS with Tide API

Once the project is created, you can build it easily with

mvn install

And then run it in Jetty (Spring or Seam) with :

cd webapp
mvn jetty:run-war

Or in the Embedded GlassFish 3.1.1 (CDI) with :

cd webapp
mvn embedded-glassfish:run

Once started you can access the default generated application at . By default there are two users created that can access the application : admin / admin and user / user.

You can then easily build a war with :

mvn war:war

Note that in this case you may have to change the configuration if your application server target is not the same as the embedded maven plugin. For example, if you target Tomcat, you will have to adapt the Gravity servlet in web.xml accordingly. Also note that the default configuration uses an embedded H2 database and locally defined security.

More details on the new features in GraniteDS 2.3

By William October 12th, 2011 Flex, GraniteDS No Comments
Please, feature an image for this post.

GraniteDS 2.3.0 RC1 is mostly a bugfix and incremental improvements release but contains a few new features :

Support for JBoss AS 7 / Hibernate 4

JBoss have once again changed their VFS internal implementation in AS 7, breaking the class scanner in GraniteDS. It was still possible to run GraniteDS in non-scan mode but this is now fixed and you can now benefit from the very fast JBoss AS 7 with GDS 2.3.

Another issue with JBoss AS 7 is its deep integration with Hibernate 4 which makes very painful to deploy Hibernate 3.x applications (i.e. all Hibernate applications). There are a few workarounds described on the Hibernate blog . However it’s recommended to upgrade to H4 as soon as possible, and GraniteDS now fully supports Hibernate 4. Just use the granite-hibernate4.jar instead of granite-hibernate.jar and you’re done.

Support for Flex 4.5+

Flex 4.5 broke a few APIs and there were two main issues with Tide :

  • The client libraries crashed when run in a mobile application
  • The PagedQuery was unusable

These two issues are now fixed and you will find a version of the granite-flex45.swc client libraries compiled with the Flex SDK 4.5 . Unfortunately it is not yet available with the ‘normal’ distribution because our build system was not able to build with two different versions of the Flex SDK. The library can be found only in the new distribution (which will be the default distribution format starting from GraniteDS 3.0). For the final release we will try to add it manually and it should be also available as a maven artifact.

Reverse lazy-loading

The support of lazy-loaded associations when retrieving detached entities from the server is an important feature of GraniteDS and greatly helps limiting the amount of data transferred through the network. However it works only in the server to client direction. The problem is that once you have loaded all associations on the client, passing an object as an argument of a remote method call will send the whole loaded object graph to the server, even if you have only changed a simple property.

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, so you can now ask Tide to uninitialize the object graph before sending it. You can do it manually with :

var uperson:Person = new EntityGraphUnintializer(tideContext).uninitializeEntityGraph(person) as Person;
personService.save(uperson);

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 = '[email protected]';
var uperson:Person = new EntityGraphUnintializer(tideContext).uninitializeEntityGraph(person) as Person;
personService.save(uperson);

Here uperson will still contain the loaded collection of contacts, but if there are other collections, they will be uninitialized.

If you want to uninitialize more than one argument, you have to use the same EntityGraphUninitializer for all so they share the same 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 need to add the [Lazy] annotation to your Flex metadata compilation configuration). Next in the Flex application, register the UninitializeArgumentPreprocessor component in Tide.

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

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

It is important to note that it will not uninitialize the associations in your ‘normal’ Tide context and that there will not be any change to your client entities. Tide will simply copy the necessary entities in a temporary context, uninitialize the associations of the copies before the passing them as arguments to the remote call and then discard everything.

Unfortunately this new feature cannot yet work with Seam context variables (and thus with the Home component). Only method arguments can be processed, but this should cover be the vast majority of use cases. Support for context variables requires a major refactoring and will come with GDS 3.0.

Injection of the Gravity singleton

In GDS 2.2 you needed a ServletContext to retrieve the Gravity singleton from a server application, which is not always possible or suitable and implies a dependency on the Servlet API. With GDS 2.3, the singleton is available in the framework context and can simply be injected.

With Spring or CDI :

@Inject
private Gravity gravity;

With Seam :

@In("org.granite.seam.gravity")
private Gravity gravity;

The DI capabilities of EJB3 are too limited to allow something like this. If you need to be independent from the Gravity API, just use a JMS topic and send messages with the JMS API.

Improved support for transparent data push (ON_COMMIT mode and non-GDS threads)

GraniteDS provides a data push feature allowing to track updates on JPA entities and transparently dispatch them in real-time through Gravity to Flex clients. However in GDS 2.2 there were two limitations : the updates could be tracked only from a thread managed by GraniteDS (an HTTP remoting or messaging request), and the ON_COMMIT mode allowing transactional dispatch of the updates through JMS was not supported out-of-the-box.

GraniteDS 2.3 comes with a set of interceptors (unfortunately one for each technology : Spring, Seam, EJB3 and CDI ; so much for interoperability for such a basic thing as an interceptor) managing the ON_COMMIT mode that can also be used to track the updates on any non-GraniteDS thread.

The setup is simple, just add the useInterceptor=true parameter on the @DataEnabled annotation and use either ON_SUCCESS or ON_COMMIT. ON_SUCCESS is the default mode and simply means that the dispatch will occur for all successful calls. ON_COMMIT means that the dispatch will occur when the transaction commits, it ensures that the dispatch is transactional when used in conjuction with a transacted JMS topic.

Then configure the interceptor for your target framework :

For Spring, add this in your context :

<graniteds:tide-data-publishing-advice/>

Take care that you have to use the latest xsd :

xsi:schemaLocation="http://www.graniteds.org/config http://www.graniteds.org/public/dtd/2.3.0/granite-config-2.3.xsd"

For Seam, nothing to do, the interceptor is implicitly setup when the @DataEnabled(useInterceptor=true) annotation is applied.

For CDI, just 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 EJB3, you have to setup the interceptor on each EJB :

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

Or globally 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>

GraniteDS, Java EE 6 and Seam 3 at JAXLondon 2011

By William March 30th, 2011 GraniteDS 1 Comment

I really enjoy going to London for . I will be speaking about Flex and Java EE 6.The timing is very interesting because Java EE 6 is now really getting a lot of traction (don’t miss the sessions of the Java EE day) and the final Seam 3.0 is about to be released. I will be able to demonstrate some nice new stuff with Flex, GraniteDS and Seam 3.I’m also happy to speak just after the keynote of from Adobe who will certainly show lots of awesome Flash & Flex demos on various mobile platforms. If I can find enough time, I’ll try to also do a short demonstration of a multiscreen Flex/AIR application on an Android phone.

See you there !

Maven archetypes updated with GDS 2.2 SP1

By William January 22nd, 2011 GraniteDS No Comments

Following the release of GraniteDS 2.2.0 SP1, we have updated the Maven archetypes (now version 1.0.0.RC2).

Unfortunately because of some weird behaviour of the Maven archetype plugin, it is now necessary to specify the archetype repository ‘central’ to be able to use the latest version : mvn archetype:generate
  -DarchetypeGroupId=org.graniteds.archetypes
  -DarchetypeArtifactId=graniteds-tide-spring-jpa-hibernate
  -DarchetypeVersion=1.0.0.RC2
  -DarchetypeRepository=central
  -DgroupId=com.myapp
  -DartifactId=example2
  -Dversion=1.0-SNAPSHOT

There have been a few changes and fixes :

  • Upgrade to GDS 2.2 SP1
  • Upgrade to flexmojos 3.8 (so mvn flexmojos:flashbuilder should work)
  • Fixed bad default package name in Spring archetype
  • Changed Java compile options to 1.6

Update 25/01/2011 : seems the central archetype catalog is now up-to-date : mvn archetype:generate
 -DarchetypeGroupId=org.graniteds.archetypes
 -DarchetypeArtifactId=graniteds-tide-spring-jpa-hibernate
 -DgroupId=com.myapp
 -DartifactId=example2
 -Dversion=1.0-SNAPSHOT

GraniteDS Maven archetypes 1.0 RC1 released

By William November 27th, 2010 GraniteDS 13 Comments

We have noticed from a long time that starting a new project with GraniteDS and the usual Java frameworks is not easy enough. We have tried to simplify the configuration as much as possible by allowing to configure GraniteDS directly in the Spring and Seam configuration files but yet there are many steps and caveeats that can take lots of time when setting up a project. It is even more painful when you add the setup of the build system with Ant or Maven.

Fortunately Maven provides a handy mechanism to bootstrap new projects in the form of archetypes. Along with the release of GraniteDS 2.2, we have also released a set of 4 archetypes for common use cases of GraniteDS :

  • graniteds-spring-jpa-hibernate: Flex 4 + Spring 3 + Hibernate/JPA + GraniteDS with standard RemoteObject API
  • graniteds-tide-spring-jpa-hibernate: Flex 4 + Spring 3 + Hibernate/JPA + GraniteDS with Tide API
  • graniteds-tide-seam-jpa-hibernate: Flex 4 + Seam 2.2 + Hibernate/JPA + GraniteDS with Tide API
  • graniteds-tide-cdi-jpa: Flex 4 + CDI/Weld 1.0 + JPA + GraniteDS with Tide API

It is recommended to use Maven 3 but Maven 2.2 should also work. You can run the archetypes from the command line :

mvn archetype:generate -DarchetypeGroupId=org.graniteds.archetypes  -DarchetypeArtifactId=graniteds-tide-spring-jpa-hibernate  -DarchetypeVersion=1.0.0.RC1  -DgroupId=com.myapp  -DartifactId=example1 -Dversion=1.0-SNAPSHOT

This will create of project named example1 that you can directly build and run with maven :

cd example1  mvn install cd webapp mvn jetty:run-war

Then browse . The generated project is a multimodule application skeleton with a Java module, a Flex module and a Web module. It includes basic security, service remoting and data synchronization. The default security configuration includes two hardcoded users: admin/admin and user/user.

It is a good starting point to build your own application, after removing the very few example parts. The most interesting thing in these archetypes is that the build poms are automatically created and that they reference selected versions of each framework (Flex, Spring, Seam, Hibernate…) and of build tools (Flexmojos, Jetty plugin…) that work together correctly.

Spring and Seam archetypes are built to be run inside Jetty (for example with the Maven Jetty plugin as shown before). The CDI/Weld example is built to be run inside GlassFish v3.0.1+ and should also work in JBoss 6 M4 (but not later as next versions include the newer and incompatible Weld 1.1). By default it can be run in embedded GlassFish with :

cd example1 mvn install cd webapp mvn embedded-glassfish:run

If you don’t want to run inside an embedded container but in Tomcat or another container, and still want to use real-time messaging, you will have to change the Gravity servlet implementation in the web.xml file (implementation class names for Tomcat and JBoss 5+ are in comment in the generated web.xml). Then build a war to be deployed in any application server with :

cd webapp mvn war:war

This is not a final release of these archetypes but they are already usable. They mostly miss the necessary Maven stuff to run unit tests for Flex and Java projects.

As always don’t hesitate to give your feedback.

Page 1 / 4