William has posted 6 posts at DZone. View Full User Profile

Enterprise RIA with Spring 3, Flex 4 and GraniteDS

01.25.2011
| 46883 views |
  • submit to reddit

Adobe Flex is one of the most widely used client technologies for building rich applications and Spring 3 is one of the most popular Java application frameworks. These two technologies make a great combination for building enterprise applications with a modern looking and rich user interface.

There are various options to integrate them, that each have their pros and cons, such as Web/REST services and the Spring-Flex project promoted by Adobe and SpringSource. There are lots of articles and resources about them, here I will focus on an alternative approach using the open source project GraniteDS.

GraniteDS is based on a cleanroom implementation of the AMF3 remoting and messaging protocols, and has been historically the first open source implementation of the AMF3 protocol in Java. It has been providing out-of-the-box integration with Spring very early and this integration has continually been improved with each new version of GraniteDS, following a few core principles :

  • Provide a fully integrated Spring/Flex/GraniteDS RIA platform that makes configuration and integration code mostly inexistant. The platform includes in particular the tools and Flex client libraries necessary to easily use all features of Spring and its associated technologies (persistence, security...).
  • Promote type-safety as much as possible in both Java and AS3 applications, ensuring that most integration issues can be detected early at compile/build time.

 

These core design choices make GraniteDS very different from for example Adobe BlazeDS that has only a server-side part. In this article I will show this concept of RIA platform at work by building a simple application using the following features :

  • Flex AMF remoting to Spring services.
  • Support for Hibernate/JPA detached entities directly in the Flex application. Bye bye DTOs and lazy initialization exceptions.
  • Support for the Bean Validation API (JSR-303) with the corresponding Flex validators.
  • Support for Spring Security 3 and Flex components that integrate with server-side authorization.
  • Support for 'real-time' data push.

 

As a side note, GraniteDS still supports the classic Flex RemoteObject API and is thus a close drop-in replacement for BlazeDS with some useful enhancements, but provides an alternative Flex API called Tide that is easier to use and brings the full power of the platform.

Project setup

We have to start somewhere, and the first step is to create the Spring application. This is no big deal, we could just start by a standard Spring MVC application, and just add a few GraniteDS elements in the Spring application context. To make things easier, I'm going to use a Maven archetype (Maven 3 recommended) :

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

 

This creates a basic project skeleton that includes persistence, security and real-time data push features. As a starting point you can simply build the project and run it with the Maven jetty plugin :

cd gdsspringflex
mvn install
cd webapp
mvn jetty:run-war
Then browse http://localhost:8080/gdsspringflex/gdsspringflex.swf, and log in with admin/admin or user/user.

 

The structure of the project is a classic multi-module Maven project with a Flex module, a Java module and a Web application module. It uses the very nice flexmojos plugin to build the Flex application with Maven.

<b>gdsspringflex</b>
|- pom.xml
|- flex
|- pom.xml
|- src/main/flex
|- Main.mxml
|- Login.mxml
|- Home.mxml
|- java
|- pom.xml
|- src/main/java
|- org/example/entities
|- AbstractEntity.java
|- Welcome.java
|- org/example/services
|- ObserveAllPublishAll.java
|- WelcomeService.java
|- WelcomeServiceImpl.java
|- webapp
|- pom.xml
|- src/main/webapp
|- WEB-INF
|- web.xml
|- dispatcher-servlet.xml
|- spring
|- app-config.xml
|- app-jpa-config.xml
|- app-security-config.xml

If we forget about the default generated application sources in the Flex and Java modules, and focus only on configuration, the most interesting files are web.xml and the app*config.xml Spring configuration files.

web.xml basically includes Spring 3 listeners, a Spring MVC dispatcher servlet mapped on /graniteamf/* that will handle AMF requests, and a Gravity servlet for Jetty mapped on /gravityamf/* (Gravity is the name of the GraniteDS Comet-based messaging implementation).

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

<display-name>GraniteDS Tide/Spring</display-name>
<description>GraniteDS Tide/Spring Archetype Application</description>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/app-config.xml,
/WEB-INF/spring/app-*-config.xml
</param-value>
</context-param>

<!-- Spring listeners -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

<!-- Spring MVC dispatcher servlet that handles incoming AMF requests on the /graniteamf endpoint -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/graniteamf/*</url-pattern>
</servlet-mapping>
<!-- Gravity servlet that handles AMF asynchronous messaging request on the /gravityamf endpoint -->
<servlet>
<servlet-name>GravityServlet</servlet-name>
<servlet-class>org.granite.gravity.jetty.GravityJettyServlet</servlet-class>
<!--servlet-class>org.granite.gravity.tomcat.GravityTomcatServlet</servlet-class-->
<!--servlet-class>org.granite.gravity.jbossweb.GravityJBossWebServlet</servlet-class-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>GravityServlet</servlet-name>
<url-pattern>/gravityamf/*</url-pattern>
</servlet-mapping></span>

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

</web-app>

 

The reason why there is a specific servlet for Gravity is that it is optimized to use the specific asynchronous capabilities of the underlying servlet container to get better scalability, and this can not be achieved with the default Spring MVC dispatcher servlet. That's also why this is necessary to configure different servlet implementations depending on the target container (Tomcat, JBossWeb...).

Next is the main Spring 3 configuration that is mostly basic Spring MVC stuff :

<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:graniteds="http://www.graniteds.org/config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.graniteds.org/config http://www.graniteds.org/public/dtd/2.1.0/granite-config-2.1.xsd">

<!-- Annotation scan -->
<context:component-scan base-package="org.example"/>

<!-- Spring MVC configuration -->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
<!-- Configuration of GraniteDS -->
<graniteds:flex-filter url-pattern="/*" tide="true"/>

<!-- Simple messaging destination for data push -->
<graniteds:messaging-destination id="welcomeTopic" no-local="true" session-
selector="true"/>
</beans>

The main thing concerning GraniteDS is the flex-filter declaration. There is also an example messaging topic that is used by the default Hello World application. app-jpa-config.xml contains the JPA configuration and does not include anything about GraniteDS. Lastly Spring Security :

<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:graniteds="http://www.graniteds.org/config"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd
http://www.graniteds.org/config http://www.graniteds.org/public/dtd/2.1.0/granite-config-2.1.xsd"
default-autowire="byName"
default-lazy-init="true">

<security:authentication-manager alias="authenticationManager">
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="admin" authorities="ROLE_USER,ROLE_ADMIN" />
<security:user name="user" password="user" authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>

<security:global-method-security secured-annotations="enabled" jsr250-annotations="enabled"/>

<!-- Configuration for Tide/Spring authorization -->
<graniteds:tide-identity/>

<!-- Uncomment when there are more than one authentication-manager :
<graniteds:security-service authentication-manager="authenticationManager"/>
-->
</beans>
Once again mostly Spring stuff, we just find here the tide-identity bean that is used to integrate Spring Security with the Tide Identity Flex component.

 

We're done for the server-side setup. GraniteDS detects automatically most of the Spring configuration at startup and configures itself accordingly, so these 10 lines of XML are generally enough for most projects. If you have a look at the various Maven POMs, you will find the dependencies on the server-side GraniteDS jars and client-side GraniteDS swcs. You can also have a look at the Flex mxml code of the example application generated by the archetype, but for now I will start from scratch.

Remoting to Spring services

First the traditional Hello World and its incarnation as a Spring 3 service :

@RemoteDestination
public interface HelloService {

public String hello(String name);
}

@Service("helloService")
public class HelloServiceImpl implements HelloService {

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

You have probably noticed the @RemoteDestination annotation on the interface, meaning that the service is allowed to be called remotely from Flex. Now the Flex application :

<s:Application
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="*"
preinitialize="init()">

<fx:Script>
<![CDATA[
import org.granite.tide.Component;
import org.granite.tide.spring.Spring;
import org.granite.tide.events.TideResultEvent;
import org.granite.tide.service.DefaultServiceInitializer;

private function init():void {
Spring.getInstance().initApplication();
Spring.getInstance().getSpringContext().serviceInitializer = new DefaultServiceInitializer('/gdsspringflex');
}

[In]
public var helloService:Component;

private function hello(name:String):void {
helloService.hello(name,
function(event:TideResultEvent):void {
message.text = "Message: " + (event.result as String);
}
);
}
]]>
</fx:Script>

<s:VGroup width="100%">
<s:Label text="Name"/>
<s:TextInput id="tiName"/>

<s:Button label="Hello" click="hello(tiName.text)"/>

<s:Label id="message"/>
</s:VGroup>

</s:Application>
You can rebuild and restart the project with :
mvn clean install
cd webapp
mvn jetty:run-war

 

Well, that's not exactly the shortest Hello World application, but let's see the interesting bits :

  • The init() method is called in the preinitialize handler. It does two things: initialize the Tide framework with Spring support, and declare a service initializer with the context root of our application. The task of the service initializer is to setup all remoting/messaging stuff, such as server endpoint uris, channels, etc... Basically it replaces the traditional Flex static services-config.xml file. Other implementations can easily be built for example to retrieve the channels configuration dynamically from a remote file (useful with an AIR application for example).
  • A client proxy for the helloService Spring bean is injected in the mxml by using the annotation [In]. By default the variable name should match the Spring service name, otherwise we would have to specify the service name in the [In("helloService")] annotation. This may seem like a 'magic' injection but as we asked for an instance of Component, the framework knows for sure that you want a client proxy for a remote bean.
  • The hello function demonstrates the basic Tide remoting API. It simply calls a remote method on the client proxy with the required arguments and provide callbacks for result and fault events, much like jQuery, so you don't have to deal manually with event listeners, asynchronous tokens, responders and all the joys of RemoteObject.

Now that we got the basics working, we can improve this a little. The Flex project POM is configured to automatically generate (with the GraniteDS Gas3 generator embedded in flexmojos) typesafe AS3 proxies for all Java interfaces named *Service and annotated with @RemoteDestination. That means we could also simply write this :

import org.example.services.HelloService;
[In]
public var helloService:HelloService;

 

This looks like a minor cosmetic change, but now you benefit from code completion in your IDE and from better error checking by the Flex compiler. Going even further, the whole injection can be made completely typesafe and not rely on the service name any more by using the annotation [Inject] instead of [In] (note that we can now give our injected variable any name) :

[Inject]
public var myService:HelloService;

private function hello(name:String):void {
myService.hello(name,
function(event:TideResultEvent):void {
message.text = "Message: " + (event.result as String);
}
);
}
And as the injection in the client now uses the interface name, we don't have to give a name to the Spring service any more :
@Service
public class HelloServiceImpl implements HelloService {

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

 

Now you can do any refactoring you want on the Java side, like changing method signatures, Gas3 will then regenerate the AS3 proxies and the Flex compiler will immediately tell you what's wrong. Another interesting thing is that the Flex mxml now looks like a Spring bean, making very easy for Spring developers to get started with Flex.

Published at DZone with permission of its author, William Draï.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Nuttapong Maneenate replied on Sat, 2011/02/12 - 9:59am

Why step mvn install it error ? [ERROR] Failed to execute goal org.sonatype.flexmojos:flexmojos-maven-plugin:3.8 :compile-swf (default-compile-swf) on project gdsspringflex-flex: Error compilin g! -> [Help 1]

Lucas Marino replied on Tue, 2011/02/22 - 11:05pm

Thank you for your work is excellent. I am interested in your articles and I would ask permission to translate into Spanish and post mentioning the source of course. If you agree email me. My email is marinoluck@gmail.com or answer me in this article. Thank you very much. Lucas Marino

Arthur Vernon replied on Wed, 2011/02/23 - 5:22pm

With the Data Push part of the tutorial, what facilities exist within the framework to identify the source of an update to avoid adding a new author twice to the list? As it currently stands, when I take this code "as is" and add a new author, the code 1. Adds the author to the authors list at creation time. 2. Adds it a second time as a result of the observer. I suppose the trusting solution is to maintain the list only via the observer.

William Draï replied on Thu, 2011/02/24 - 3:55pm in response to: Nuttapong Maneenate

@Nuttapong : Not sure what happens here, maybe try to reset you maven repo.

@Arthur : Right, the recommended solution is to keep only the observer. Unfortunately there is currently no way of knowing where the update comes from (local, remoting or push).

Chris Jansen replied on Fri, 2011/02/25 - 11:24am

I had to specify a version for maven-jetty-plugin to get it to run inside of Spring STS IDE.  Great tutorial too - thank for writing it up!

Richard Van Der Laan replied on Tue, 2011/03/08 - 6:09am

Hi there,

I am unable to extract the extended data from the validation exception, as described in the example:

function(event:TideFaultEvent):void {

    if (event.fault.faultCode == 'Validation.Failed') {        // Do something interesting, for example show the first error message-->        Alert.show(event.fault.extendedData.invalidValues[0].message);    }}
 The problem is that fault (type mx.rpc.Fault) has no extendedData property and the property cannot dynamically be accessed. Is this due to an API change? Or am I doing something wrong?

Deepak Srivastav replied on Fri, 2011/03/11 - 8:01am

my entity attached with a form is not updated in the database ie the effect of updating entity in flex side does not update the entity the method of service is called each time but the entity remained same .I will greatly appreciate any kind of help

iain starks replied on Fri, 2011/04/01 - 5:43am

I failed to do jetty:run-war after creating the archetype, getting a java.lang.NoSuchMethodError: javax.persistence.spi.PersistenceUnitInfo.getValidationMode()Ljavax/persistence/ValidationMode; The version ins persistence-api 1.0 doesn't have this method, but it was being pulled in as well from the hibernate-jpa-2.0-api-1.0.0.Final.jar So I removed the dependency: javax.persistence persistence-api 1.0 From the java pom and re-build and that seemed to get me going.

Eric Be replied on Mon, 2011/05/02 - 11:12pm

Hi, Great article! I was wondering if there was a way to access your completed exmaple/application to see the entire sources of everything. I can't find a d/l link anywhere in the tutorial and the archetype project doesn't include any of your more detailed classes/interfaces/etc. Thanks! Eric

Lou Leal replied on Mon, 2011/07/18 - 3:18pm in response to: Richard Van Der Laan

According to this http://www.graniteds.org/jira/browse/GDS-846 this error was fixed in 2.2.-_SP2 but I updated my pom to this version of granite and the same error occurs. I guess it didn't get fixed...

Dmitry Kv replied on Tue, 2011/08/09 - 11:03am in response to: Lou Leal

should be
(event.extendedData.invalidValues[0].message);

Khent Johnson replied on Fri, 2011/09/02 - 2:41pm

Hello! Just got in here. I used the most recent release of GraniteDS and same with Eric I'd like to access your complete example and if possible could anyone share the links of guide related to the topic. I'll be glad and thankful to any response. Cheers! GAR Labs

Sanjay Patel replied on Wed, 2012/08/29 - 7:53am

Thanks. This is very nice article to get started. 

Can you please add or guide how to get flexmojos wrapper goal working? 

Gary Huitson replied on Wed, 2012/09/12 - 10:03am in response to: Richard Van Der Laan

I think theres a mistake in the example code

The line should read...

Alert.show(event.extendedData.invalidValues[0].message);  

The extendedData is a property of event not event.fault 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.