Spring 3 Integration with BlazeDS 3.2 for RIA development.

Very recently Spring BlzaeDS Integration 1.0.3 (SBI)has been released. I’ve been in the Flex 3 programming over  13 months (Flex 3 finally released in Feb 2008) and amazing about the capabilities. As a Java Programmer, I have a lots of experiences in Java Swing application development (using Eclipse Visual Editor) but Flex is the easiest so far found with great capabilities. As a Java programmer, for 10 years I suffered with lack of rich GUI facilities in Java (Swing and AWT very good but not rich as Flex).

It is very rear to find a ERP which is based on the RIA capabilities. Fortunately, I found a ERP system which is supposed to migrate to RIA platform and I was selected as a chief consultant to define a RIA platform for this ERP.

Architecture

The Flex based solution is capable of provide a thin rich client on interne browsers. In addition to that client is portable across any browser were Flash 10 supports. The backend is based on Java EE enterprise solution. Adobe has highly recommended to use Spring framework from her number of Java integration solutions. The new release of the SBI has a far easy bridging  solution to integrate Flex and Spring.

eFiNac Architecture

Fig. 1: Architecture pattern

As shown in the above figure, Flash client directly communicate with Java business services via AMF protocol. The AMF is Adobe proprietary protocol which is highly secured to communicate over the Internet. In addition to that, magnificent performance improvement can be achieved.

Tools

Spring has released STS which is the best to develop, however, with this blog, I will introduce how to use Eclipse for development of server side. Download the latest SpringIDE plug-in and install in your Eclipse IDE(or go head with STS). In addition to that you have to have Flex 3 Builder if you are new to Flex, otherwise you can use Flex SDK 3.

Getting started

First of all you need to import blazeds.war as a Dynamic web project in the Eclipse for Java EE. The file blazeds.war available in the BlazeDS 3.2.0 downloaded zip file. Now you are ready for Spring configuration for web project. First of all you have to import blazeds.war file as a dynamic web project into the Eclipse workspace with your own name, for example, “SpringFlexExample” in my case. Add the “Spring nature” before proceed by right click and select “Spring tools” from the project.

Configuration

In the Eclipse IDE, your current project you have to convert as a persistence project.

 Project Facete for JPA

Fig 2: Project Facets

Eclipse current version is based on JPA 1.0 which is older and new released for JPA 2.0 is available. The well known Hibernate isn’t available for the JPA 2.0 when I am writing this blog. Therefore, I used EclipseLink 2.0 which is the best at the moment. As a database, used Derby 10 which is simple to start and stop and it is a EclipseLink tested DBMS for JPA.

Persistence configuration

Fig 3: Persistence configuration for JPA 2

As shown in the above Fig 3, you have to create new “User Library” that is configured to the EclipseLink 2.0. In the same window, you can create a new database connection, for example, “Cagelogue” in my case, which is used to synchronized persistency classes with the DBMS. For synchronize, you need to select the persistence.xml and right click for the popup menu, form the popup, select the “Synchronized Class List”.

lib left lib right

Fig 4: Libraries

In the above list, Most of the security framework related jar files can be avoided. For example, case, ldap and openid libraries.

Tomcat 6 configuration for Spring use is already explained in the previous blog. Let’s see how to configure Spring to web project which is created from BlazeDS. Spring and BlazeDS configured via SBI.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>

    <display-name>Spring BlazeDS Sample</display-name>
    <description>Spring BlazeDS Sample Application</description>
    <context-param>
           <!-- param-name>services.configuration.file</param-name-->
           <!-- param-value>/WEB-INF/flex/services-config.xml</param-value-->
           <param-name>contextConfigLocation</param-name>
           <param-value>/WEB-INF/config/*-config.xml</param-value>
    </context-param>
 <filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>
 
 <filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
 

    <!-- Http Flex Session attribute and binding listener support -->
 <listener>
  <listener-class>flex.messaging.HttpFlexSession</listener-class>
 </listener>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
 

 
 
    <servlet>
         <servlet-name>flex</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <!-- servlet-name>MessageBrokerServlet</servlet-name-->
        <!-- url-pattern>/messagebroker/*</url-pattern-->
     <servlet-name>flex</servlet-name>
     <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>


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


    <!-- for WebSphere deployment, please uncomment -->
    <!--
    <resource-ref>
        <description>Flex Messaging WorkManager</description>
        <res-ref-name>wm/MessagingWorkManager</res-ref-name>
        <res-type>com.ibm.websphere.asynchbeans.WorkManager</res-type>
        <res-auth>Container</res-auth>
        <res-sharing-scope>Shareable</res-sharing-scope>
    </resource-ref>
    -->
    

  <!-- app configure database -->
  <resource-ref>
 <res-ref-name>jdbc/catalogueDB</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
  </resource-ref>

  <resource-ref>
 <res-ref-name>jdbc/securityDB</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
  </resource-ref>
</web-app>

Listing 1: Web.xml

As shown in the above listing, Spring security 3 has been used to secure the application. More comprehensive configuration details can be found in the SBI user reference. In the top of the listing, security filter chains are configured first. In this example, security is database based, therefore “jdbc/securityDB” has been referenced which has been primarily defined in the context.xml. Then Spring MVC controllers to support to multiple client-types. The two Spring configuration files are included in this example, but should be postfix as “-config”. These information is given under the <context-param>:  One file for the application configuration and other for the security configuration.

Example application

As shown in the Fig. 1, GuestListImpl implementation of the GuestService interface.

package com.example.service;

import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import javax.annotation.Resource;

import net.sf.jasperreports.engine.JRException;

import com.example.dao.GuestDao;
import com.example.domain.Guest;

public class GuestListImpl implements Serializable, GuestService {

 @Resource(name="jpaGuestDao")
 GuestDao guestDao;
 
 public List<Guest> getGuestList() {
  try{
  return guestDao.listGuests();
  } catch(Exception e){
   e.printStackTrace();
  }
  return null;
 }

 @Override
 public void registerGuest(Guest guest) {
  try{
  guestDao.addGuest(guest);
  } catch (Exception e){
   e.printStackTrace();
  }
  
 }
 
 @Override
 public String getGuestListReport() throws IOException, JRException{

  return guestDao.generateGuestsReport();
 }

 @Override
 public void updateGuest(Guest guest) {
  guestDao.update(guest);
  
 }

}

Listing 2: GuestListImpl.java

For simplicity, I’ve limited the number of methods to be 4 for CURD operations; registerGuest() – create new guest, updateGuest() – update existing guest, getGuestList() – read guest and extra method for report generation using JasperReports.  All these methods are business service methods, and need GuestDao for data access at the next layer (in the Data). To transform data, between server and the client, a domain object “Guest” has been created. The class Guest is a serialized object carrying data about guest for transfer to Flex client for retrieval and visa versa. However, the conversion process is completely transparent.

package com.example.dao;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.query.JRJpaQueryExecuterFactory;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.Guest;

@Repository
public class JpaGuestDao implements GuestDao {

 @PersistenceContext
 EntityManager em;
  
 @Resource(name="reportLocation")
 ReportLocation repLoc;

 @Override
 @Transactional
 public List<Guest> listGuests() {

  List<Guest> guestList = em.createNamedQuery("listAllGuest")
    .getResultList();
  return guestList;
 }

 @Override
 @Transactional(propagation=Propagation.REQUIRED)
 public void addGuest(Guest guest) {
 
  em.persist(guest);
 }
 
 @Override
 @Transactional
 public String generateGuestsReport() throws IOException, JRException {
  Map parameterMap = new HashMap();
  parameterMap.put(JRJpaQueryExecuterFactory.PARAMETER_JPA_ENTITY_MANAGER, em);


  
   org.springframework.core.io.Resource res = repLoc.getResource();
   File file = res.getFile(); 
   System.out.println(file.getAbsolutePath());
   System.out.println(res.getURL());
   
   System.out.println("read file");
   InputStream in = this.getClass().getResourceAsStream("/../reports/systemusers.jasper");
   System.out.println("get Print");
   JasperPrint print = JasperFillManager.fillReport(in, parameterMap);
   System.out.println("start Export...");
   JRPdfExporter pdfExporter = new JRPdfExporter();
   System.out.println("set params");
   pdfExporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
   //ByteArrayOutputStream out = new ByteArrayOutputStream();
   //pdfExporter.setParameter(JRExporterParameter.OUTPUT_STREAM, out);
   String pdfName = "test.pdf";
   File reportDir= new File(file.getAbsoluteFile()+"/"+repLoc.getReportDirectory());
   File reportFile = new File(file.getAbsoluteFile()+"/"+repLoc.getReportDirectory()+"/"+pdfName);
   if (!reportDir.exists()){
    reportDir.mkdir();
   }
   if (!reportFile.exists()){
    System.out.println("create new file..."+reportFile.getAbsolutePath());
    reportFile.createNewFile();
   }
   pdfExporter.setParameter(JRExporterParameter.OUTPUT_FILE, reportFile);
   System.out.println("Export ...");
   throw new JRException("Test");
   //pdfExporter.exportReport();
   //return repLoc.getReportHost()+"/"+repLoc.getReportDirectory()+"/"+pdfName;//out.toByteArray();
  
 
 }

 @Override
 @Transactional
 public void update(Guest guest) {
  System.out.println("Id "+guest.getId()+"Name "+guest.getGuestName() );
  Guest g = em.find(Guest.class, guest.getId());
  if (g!=null){
   System.out.println("merge guest...");
   
   em.merge(guest);
  } else System.out.println("not found...");
  
 }

}

Listing 3: JpaGuestDao.java

The Dao shown in the above listing is the implementation of GuestDao interface. Forgive me for the spaghetti code. It is pretty clear everything except the method generateGuestReport() which return the report hosting URL. I had a big fight to create a report location, therefore created a dummy class com.example.dao.ReportLocation which has only two attributes; reportHost and the report directory injected values via Spring configuration deliberately and ultimately created a report hosting URL in the method. However, you can refactor this code separating responsibility of define host host in Flex client using Flex resource bundles and report location/name only in the Dao. Although my way is not elegant, I decided to survive with that to shorten the this lengthy blog.

Spring configuration

Time to introduce Spring configuration;

<?xml version="1.0" encoding="UTF-8"?>
<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:flex="http://www.springframework.org/schema/flex" 
 xmlns:jee="http://www.springframework.org/schema/jee"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:sec="http://www.springframework.org/schema/security"
 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.springframework.org/schema/flex 
       http://www.springframework.org/schema/flex/spring-flex-1.0.xsd
      http://www.springframework.org/schema/jee 
       http://www.springframework.org/schema/jee/spring-jee-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/security 
       http://www.springframework.org/schema/security/spring-security-3.0.xsd">


 <bean id="logginInterceptor" class="com.example.intercept.LoggingInterceptor"/>
 
 <bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
  <property name="dataSource" ref="securitySource"/>
  <property name="enableAuthorities" value="false"/>
  <property name="enableGroups" value="true"/>
 </bean>

 <!-- Flex - Application specific -->
 <bean id="guestListService" class="com.example.service.GuestListImpl">
  <sec:intercept-methods>
   <sec:protect access="ROLE_ADMIN" method="registerGuest"/>
   <sec:protect access="ROLE_USER" method="getGuestList"/>
  </sec:intercept-methods>
 </bean>

 <!-- JPA - Application specific -->
 <bean id="jpaGuestDao" class="com.example.dao.JpaGuestDao" />

 <!-- Reporting location -->
 <bean id="reportLocation" class="com.example.dao.ReportLocation">
  <constructor-arg value="/" />
  <property name="reportHost" value="http://ojitha-pc:8080/SpringFlexExample" />
  <property name="reportDirectory" value="reports" />
 </bean>

 <!-- JPA common configuration -->
 <tx:annotation-driven />
 <bean
  class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
 <context:annotation-config />

 <bean id="jpaAdapter"
  class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
  <property name="databasePlatform"
   value="org.eclipse.persistence.platform.database.DerbyPlatform" />
  <property name="showSql" value="true" />
  <property name="generateDdl" value="true" />
 </bean>

 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory" />
  <property name="dataSource" ref="dataSource" />
 </bean>

 <context:load-time-weaver/>

 <bean id="entityManagerFactory"
  class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter" ref="jpaAdapter" />
  <property name="persistenceUnitName" value="SpringFlexExample" />
  <property name="persistenceUnitManager" ref="PUnitMgr" />
 </bean>

 <jee:jndi-lookup id="dataSource" jndi-name="jdbc/catalogueDB" />
 <jee:jndi-lookup id="securitySource" jndi-name="jdbc/securityDB" />

 <bean id="PUnitMgr"
  class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
  <property name="persistenceXmlLocations">
   <list>
    <value>/WEB-INF/classes/META-INF/persistence.xml</value>
   </list>
  </property>
  <property name="defaultDataSource" ref="dataSource" />
 </bean>

 <bean
  class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

</beans>

Listing 4: web-application-config.xml

This file should be available in the WEB-INF/config directory and postfix with “-config” according to the web.xml file configuration of <context-param>.  Let’s start from the end of the file.

First you have to specify the persistence unit manager. Persistence Unit Manager needs two prosperities to fulfil, those are underline data source and the location of the persistence.xml file, in this example it is in the /WEB-INF/classes/META-INF directory. the “datasource” is JNDI lookup based, and the jndi-name should be available in the web.xml (Listing 1) file.

Next is a JPA adapter. The adapter values are different for different JPA implementations and databases. In this case we are using EclipseLink and Derby database. The adapter configuration is vender specific (I am not happy with this), although it uses Spring classes. Next is the entity manager factory which is nothing other than consolidating everything in the configuration perspective. The transaction manager is the most important actor in the Dao who takes the responsibility of executing CURD operations and JPQL queries. Entity manage will be available to Dao via @persistenceContext as shown in the Listing 3.

Security

Security has been configured in the different configuration file (thanks to Spring great flexibility of configuration).

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:sec="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <sec:http entry-point-ref="entryPoint" >
  <sec:anonymous enabled="false"/>
 </sec:http>
 <bean id="entryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/> 
 
 <bean id="security3Helper" class="com.example.security.Security3Helper"/>

 <sec:authentication-manager alias="authenticationManager">
  <sec:authentication-provider user-service-ref="userDetailsService" />
 </sec:authentication-manager>
 

</beans>

Listing 5: web-security-config.xml

First, anonymous user entry is prohibited. Then we have to configure the HTTP based security for the authentication.

package com.example.security;

import java.util.Map;

import org.springframework.flex.security3.AuthenticationResultUtils;

public class Security3Helper {

    public Map<String, Object> getAuthentication() {
        return AuthenticationResultUtils.getAuthenticationResult();
    }
}

Listing 6: Security3Helper.java

As shown in the Listing 6, the Security3Helper class will manage the authentication of the system when interacting with Flex client. See the Listing 13 to understand the use of Security Helper class. The remote object id for the security helper can be any name, but the “destination” should be defined in the flex-servlet.xml(Listing 8) file as a remote service. For authentication, getAuthenticaton() method should be access which is defined in the Security3Helper class as shown in the listing 6. Before access the secured system resources, the system authentication should be successful. For the authentication, authentication manager has been defined using “userDetailsService” which is defined in the web-application-config.xml (Listing 4) file. The security realm is based on the database, and I used a advanced data structure to create role based authentication as shown in the Fig 5.

Security  

Fig 5: Security realm

It is best practice to keep security realm out of the main application. Therefore, I’ve used “securitySoruce” for “dataSource” property which is entirely base on another database.  To create this datastructure you have to execute the following SQL queries.

CREATE TABLE "PUBLIC"."GROUPS"
(
   ID bigint PRIMARY KEY NOT NULL,
   GROUP_NAME varchar NOT NULL
)
;
CREATE UNIQUE INDEX SYS_IDX_48 ON GROUPS(ID)
;
CREATE TABLE "PUBLIC"."GROUP_MEMBERS"
(
   ID bigint PRIMARY KEY NOT NULL,
   USERNAME varchar NOT NULL,
   GROUP_ID bigint NOT NULL
)
;
ALTER TABLE GROUP_MEMBERS
ADD CONSTRAINT FK_GROUP_MEMBERS_GROUP
FOREIGN KEY (GROUP_ID)
REFERENCES GROUPS(ID)

;
CREATE INDEX SYS_IDX_55 ON GROUP_MEMBERS(GROUP_ID)
;
CREATE UNIQUE INDEX SYS_IDX_53 ON GROUP_MEMBERS(ID)
;
CREATE TABLE "PUBLIC"."GROUP_AUTHORITIES"
(
   GROUP_ID bigint NOT NULL,
   AUTHORITY varchar NOT NULL
)
;
ALTER TABLE GROUP_AUTHORITIES
ADD CONSTRAINT FK_GROUP_AUTHORITIES_GROUP
FOREIGN KEY (GROUP_ID)
REFERENCES GROUPS(ID)

;
CREATE INDEX SYS_IDX_51 ON GROUP_AUTHORITIES(GROUP_ID)
;

Listing 7: SQL queries for security realm

The above realm created in the HSQLDB database. Need to disable the authorities and enable the groups as show in the Listing 4.

As shown in the Listing 4, “guestListService” operations are secured, as follows

Operation^/Role >

ROLE_ADMIN

ROLE_USER

registerGuest

X

-

getGuestList

-

X

Table 1: Group based ACL for operations

As shown in the above table, the operation registerGuest() can be used only by the users assigned as a ROLE_ADMIN. This is not the end…

Flex Configuration

Let’s see the Flex BlazDS configuration with Spring. The services should be open up as flex remote services.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:flex="http://www.springframework.org/schema/flex"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/flex 
  http://www.springframework.org/schema/flex/spring-flex-1.0.xsd">
 
  
 <flex:message-broker>
  <flex:message-interceptor ref="logginInterceptor"/>
  
  <flex:exception-translator ref="globalException"/>
  <flex:exception-translator ref="accessException"/>
  <flex:exception-translator ref="lockingException"/>
  <flex:message-service
   default-channels="my-amf, my-streaming-amf,my-longpolling-amf,my-polling-amf"/>
  <flex:secured/>
 </flex:message-broker>
 
 <!-- exceptions -->
 <bean id="globalException" class="com.example.flex.exception.GlobalGenericException"/>
 <bean id="accessException" class="com.example.flex.exception.AccessException"/>
 <bean id="lockingException" class="com.example.flex.exception.OptimisticLockingException"/>

 <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  <property name="mappings">
   <value>
    /*=_messageBroker
   </value>
  </property>
  
 </bean>

 <!-- Dispatches requests mapped to a MessageBroker -->
 <bean class="org.springframework.flex.servlet.MessageBrokerHandlerAdapter" />
 
 <flex:remoting-destination ref="guestListService" />
 <flex:remoting-destination ref="security3Helper"/>
</beans>

Listing 8: flex-servlet.xml

Both the Spring services, “guestListService” and “security3Helper” have been open up as flex services to accessed by the Flex client.

Exception Handling

Exception handling is one of the very interesting thing to discussed. For, example, suppose you don’t have enough access rights for the particular resource but you tried. Then security system will raised runtime exception that is a instance of the org.springframework.security.access.AccessDeniedException by the Spring Security. To catch this exception and transfer to Flex client, there should be a exception translator as shown in the following example.

package com.example.flex.exception;

import org.springframework.flex.core.ExceptionTranslator;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.ClassUtils;

import flex.messaging.MessageException;

public class AccessException implements ExceptionTranslator {

 @Override
 public boolean handles(Class<?> clazz) {
  return (ClassUtils.isAssignable(AccessDeniedException.class,clazz));
 }

 @Override
 public MessageException translate(Throwable th) {
  MessageException ex =new MessageException();
  ex.setCode("Application.Service");
  ex.setMessage("Access Denied for this operation");
  ex.setRootCause(th);
  return null;
 }

}

Listing 9: AccessException.java exception translator

The AccessException class is a translator as well as it is a handler. The method handler() verifies that the exception can be handle by the translator. If so, translate() method translates the exception to the meaningful message before sending to the Flex client. The configuration of the exception translator should be noted in the flex-servlet.xml file as shown in the Listing 8.

Interceptors

Interceptors are optional but very interesting topic when it come to advanced programming. It create a path to create great logger system to monitor and capture each operation, for example. Interceptors are capable of intercepting incoming request as well as out going response as shown in the following listing,

package com.example.intercept;

import org.springframework.flex.core.MessageInterceptor;
import org.springframework.flex.core.MessageProcessingContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import com.example.domain.Guest;
import flex.messaging.messages.Message;

public class LoggingInterceptor implements MessageInterceptor {

 @Override
 public Message postProcess(MessageProcessingContext ctx, Message inputMsg,
   Message outputMsg) {
  System.out.println("post class " + outputMsg.getClass().getName());
  if (inputMsg instanceof flex.messaging.messages.RPCMessage) {
   flex.messaging.messages.RPCMessage info = (flex.messaging.messages.RPCMessage) inputMsg;
   // System.out.println("user Id:"+info.getRemotePassword());
  }

  if (outputMsg instanceof flex.messaging.messages.AcknowledgeMessage) {
   flex.messaging.messages.AcknowledgeMessage info = (flex.messaging.messages.AcknowledgeMessage) outputMsg;
   // System.out.println("user Id:"+info.getRemotePassword());
   SecurityContext sc = SecurityContextHolder.getContext();
   if (sc != null) {

    Authentication a = sc.getAuthentication();
    if (a != null) {
     System.out
       .println("******** post authentication is not null *******");
     System.out.println("user name: " + a.getName());
     Object obj = outputMsg.getBody();
     System.out.println(obj);
    } else
     System.out.println("authentication is null");
   } else
    System.out.println("sc is null");
  }
  return outputMsg;
 }

 @Override
 public Message preProcess(MessageProcessingContext ctx, Message msg) {
  System.out.println("pre class " + msg.getClass().getName());
  if (msg instanceof flex.messaging.messages.CommandMessage) {
   flex.messaging.messages.CommandMessage cmdMsg = (flex.messaging.messages.CommandMessage) msg;
   cmdMsg
     .getHeader(flex.messaging.messages.CommandMessage.REMOTE_CREDENTIALS_HEADER);

  }
  if (msg instanceof flex.messaging.messages.RPCMessage) {
   flex.messaging.messages.RPCMessage info = (flex.messaging.messages.RPCMessage) msg;
   // System.out.println("user Id:"+info.getRemotePassword());
   SecurityContext sc = SecurityContextHolder.getContext();
   if (sc != null) {

    Authentication a = sc.getAuthentication();
    if (a != null) {
     System.out
       .println("******** pre authentication is not null *******");
     System.out.println("user name: " + a.getName());
     Object obj = msg.getBody();
     System.out.println(obj);
     if (obj instanceof com.example.domain.Guest) {
      Guest g = (Guest) obj;
      System.out.println(g.getGuestName());
     }
    } else
     System.out.println("authentication is null");
   } else
    System.out.println("sc is null");
  }

  return msg;
 }

}

Listing 10: LogginInterceptor.java

There are two methods for pre-processing and post processing. This has to be configured in the flex-servlet.xml file as a message interceptor as shown in the listing 8.

Flex Client

The first thing is create a ActionScript 3 class complement to the domain object. The Java based Guest domain class is simple;

package com.example.domain;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Version;

@Entity
@NamedQuery(name="listAllGuest", 
  query="SELECT g FROM Guest g")
public class Guest implements Serializable{
 @Id
 private int id;
 
 @Version
 private int version;
 
 public int getVersion() {
  return version;
 }

 public void setVersion(int version) {
  this.version = version;
 }

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 private String guestName;
 
 public String getGuestName() {
  return guestName;
 }

 public void setGuestName(String guestName) {
  this.guestName = guestName;
 }


}

Listing 11: Guest.java

The class should be serialized object with necessary named queries. The relevant AS3 class is as follows, but no need of the getter/setter methods.

package 
{
 [Bindable]
 [RemoteClass(alias="com.example.domain.Guest")]
 public class Guest
 {
   public var id:int;
   public var guestName:String;
   public var version:int;

 }
}

Listing 12: Guest.as

Important thing to note that is the alias which should be mapped to the Java domain object. Version is an attribute to maintain the data integrity in the long conversation (for the optimistic locking).

Let’s go through the client AS3 code;

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="728" height="628" xmlns:net="flash.net.*"
 applicationComplete="applicationCompleteHandler()">
 
 <mx:RemoteObject id="ro" 
  destination="guestListService" showBusyCursor="true"
  endpoint="http://localhost:8080/SpringFlexExample/messagebroker/amf">
  <mx:method name="updateGuest" 
   result=""
   fault="faultHandler(event)"/> 
  <mx:method name="getGuestList" 
   result="resultHandler(event)"
   fault="faultHandler(event)"/>
  <mx:method name="registerGuest"
   result="regResultHandler(event)"
   fault="regFaultHandler(event)"/>
  <mx:method name="getGuestListReport"
   result="resultPdf(event)"
   fault="faultPdf(event)"/>

 </mx:RemoteObject>
 <mx:RemoteObject id="securityHelper" 
   destination="security3Helper"
   endpoint="http://localhost:8080/SpringFlexExample/messagebroker/amf">
  <mx:method name="getAuthentication" 
   result="userHandler(event)"
   fault="faultHandler(event)"/>
 </mx:RemoteObject>

 <mx:Script>
  <![CDATA[
   import mx.messaging.events.MessageEvent;
   import mx.messaging.errors.MessagingError;
   import mx.events.DataGridEventReason;
   import mx.events.ListEvent;
   import mx.events.DataGridEvent;
   import mx.rpc.AsyncResponder;
   import mx.rpc.AsyncToken;

  import mx.collections.ArrayCollection;
  import mx.rpc.events.ResultEvent;
  import mx.rpc.events.FaultEvent;
  import mx.utils.ObjectUtil;
  import mx.controls.Alert;
  import mx.utils.StringUtil;
  
  [Bindable]
  private var guestListDP:ArrayCollection = new ArrayCollection();
  [Bindable]
  private var user:Object=null;
  
  private var repUrl:String;
  private var req:URLRequest;
  
  private function resultPdf(event:ResultEvent):void{
    repUrl = event.result.valueOf();
    req = new URLRequest(repUrl);
    Alert.show(repUrl);
  }
  
  private function btn_click(evt:Event):void{

   fileReference.download(req);
  }
  
  private function faultPdf(event:FaultEvent):void{
   Alert.show(event.fault.faultString);
  }
  private function resultHandler(event:ResultEvent):void {
   guestListDP = ArrayCollection(event.result);
  }
  
  private function faultHandler(event:FaultEvent):void {
   
   Alert.show(event.fault.faultString);
  }
  
  private function regFaultHandler(event:FaultEvent):void {
   Alert.show( ObjectUtil.toString(event.fault) );
  }
  
  private function regResultHandler(event:ResultEvent):void {
   Alert.show( ObjectUtil.toString(event.result) );
  }
  
  private function registerGuest():void{
   var aGuest:Guest = new Guest();
   aGuest.id= int(guestId.text);
   aGuest.guestName=guestName.text;
   ro.registerGuest(aGuest);
  }
  
  private function applicationCompleteHandler():void
  {
   securityHelper.getAuthentication();

  }
  
  private function userHandler(event:ResultEvent):void 
  {
   user = event.result;
   if (user != null) {
    userId.text = user.name;
    userId.editable = false;
    password.editable = false;
   }
  } 
  
  private function loginUser():void
   {
    var token:AsyncToken = securityHelper.channelSet.login(userId.text, password.text);
      token.addResponder(
       new AsyncResponder(
        function(event:ResultEvent, token:Object = null):void{
         user = event.result;
         userId.editable = false;
         password.editable = false;
        },
        function(event:FaultEvent, token:Object = null):void{
         Alert.show(event.fault.faultString, "Login Failed");
        }
       )
      );
   }

   private function logoutUser():void
   {
    securityHelper.channelSet.logout();
    user = null;
    userId.text = "";
    userId.editable = true;
    password.text = "";
    password.editable = true;

   }

   [Bindable]
   private var editedGuest:Array = new Array();
   private function updateGuest(evt:DataGridEvent):void{
    trace(evt.reason);
    
    var g:Guest= evt.itemRenderer.data as Guest;
    editedGuest[evt.rowIndex]=g;
   }
   private function updateGuests(){
    for each (var g:Guest in editedGuest){
     ro.updateGuest(g);
    }
    
   }

  ]]>
 </mx:Script>

 <mx:Panel title="RemoteObject Example"
  paddingLeft="10" paddingRight="10" paddingBottom="10" paddingTop="10"
  horizontalAlign="center" width="416" height="377" visible="{user!=null ? true: false}">

  <mx:Button label="Get Guest List" click="ro.getGuestList()"/>
 
  <mx:DataGrid id="dg" dataProvider="{guestListDP}" width="260" height="180" editable="true"
    itemEditEnd="updateGuest(event)">
   <mx:columns>
    <mx:DataGridColumn headerText="Id" dataField="id" editable="false" visible="{user !=null ? user.authorities.indexOf('ROLE_ADMIN')>=0: false}"/>
    <mx:DataGridColumn headerText="Name" dataField="guestName" editable="true" />
    <mx:DataGridColumn dataField="version" editable="false"/>
   </mx:columns>
  </mx:DataGrid>
  <mx:Button label="Update" click="updateGuests()"/>
 </mx:Panel>
 <mx:Form x="474" y="36">
  <mx:FormHeading label="Add Guest"/>
  <mx:FormItem label="Id">
   <mx:TextInput id="guestId"/>
  </mx:FormItem>
  <mx:FormItem label="Name">
   <mx:TextInput id="guestName"/>
  </mx:FormItem>
  <mx:FormItem>
   <mx:Button label="Register" click="registerGuest();"/>
  </mx:FormItem>
 </mx:Form>
 
 <net:FileReference id="fileReference">
  
 </net:FileReference>
 <mx:Button x="474" y="180" label="PDF" click="ro.getGuestListReport()"/>
 <mx:Button x="474" y="210" label="download" click="btn_click(event)"/>
 <mx:Form x="442" y="240" width="252" height="130">
  <mx:FormItem label="User Id">
   <mx:TextInput id="userId"/>
  </mx:FormItem>
  <mx:FormItem label="Password">
   <mx:TextInput id="password"/>
  </mx:FormItem>
  <mx:Button label="Login" id="login" click="loginUser()"/>
  <mx:Button label="Logout" id="logout" click="logoutUser()"/>
 </mx:Form>
    <mx:ArrayCollection id="eventsArrColl" />
    <mx:DataGrid id="eventsDataGrid"
            dataProvider="{editedGuest}"
            itemRenderer="mx.controls.Label"
            verticalScrollPolicy="on"
            rowCount="9"
            width="100%" x="10" y="401">
        <mx:columns>
            <mx:DataGridColumn dataField="id" />
            <mx:DataGridColumn dataField="guestName" />
            <mx:DataGridColumn dataField="version"/>
         </mx:columns>
    </mx:DataGrid>

</mx:Application>

Listing 13: Client.mxml 

As shown in the above listing, endpoint of the remote object “ro” is hardcoded for simplicity. But using resource bundles you can move the hardcoded stuff to property files.

As explained security service access, to access remote service it should be defined in the flex-servlet.xml. For example, in  listing 13, we are going to access “guestListService” which is already defined as a remote service in the listing 8. In addition to that result and fault methods should be declared for each and every method. For example, when access “getGuestList” method, if there is no fault, the “resulthandler(event)” method is executed otherwise “faulthander()” must execute.

At the end of this lengthy blog, I would like to show you the complete working example.

 

Clip 1: The access of getGuestList() and registerGuest() remote methods

In the second clip, I tried to explain the real use of the role based security using Spring 3 Security. In addition to the authentication, its provide authorization Server level and Client level (see line #164 in the listing 13).

 

Clip2: Spring 3 Security Role-based

Your reviews are welcome. Thanks for reading this blog.

Comments

Popular posts from this blog

How To: GitHub projects in Spring Tool Suite

Spring 3 Part 7: Spring with Databases

Parse the namespace based XML using Python