Pages

Showing posts with label Adobe Flex. Show all posts
Showing posts with label Adobe Flex. Show all posts

Tuesday, May 11, 2010

Using Remote Development Services in Flash Builder 4 and BlazeDS

In Adobe Flex Builder 3 it could be hard to map the data of a remote object or a web service to a FLEX component like a datagrid. With the help of a RDS Servlet in combination with BlazeDS or LifeCycle Data Services you can connect from Flash Builder 4 to your Back End Web Application and generate the required client side code and map this to a Flex component. This also works for a XML file or a Rest Service ( need crossdomain security configured ).

Before you can use it in FB4  you need to download BlazeDS 4 ( Binary Distribution ) and Adobe LifeCycle Data Services ( you only need the flex-rds-lcds.jar ) Add these jars to the WEB-INF/lib folder and provide the BlazeDS configuration files. It will look like this..
 Configure the web.xml with the required RDS and BlazeDS servlets and mappings.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xmlns="http://java.sun.com/xml/ns/javaee" 
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
   id="WebApp_ID" version="2.5">
  <display-name>FlashBuilderWeb</display-name>
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
       </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>RDSDispatchServlet</servlet-name>
        <servlet-class>flex.rds.server.servlet.FrontEndServlet</servlet-class>
  <init-param>
   <param-name>useAppserverSecurity</param-name>
   <param-value>false</param-value>
  </init-param>        
        <load-on-startup>10</load-on-startup>
    </servlet>

    <servlet-mapping id="RDS_DISPATCH_MAPPING">
        <servlet-name>RDSDispatchServlet</servlet-name>
        <url-pattern>/CFIDE/main/ide.cfm</url-pattern>
    </servlet-mapping>
    
    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
    
    <listener>
        <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
</web-app>

As a demo I created simple  getCities JAX-WS service and added this as a destination in the proxy-config.xml of BlazeDS.
package nl.whitehorses.ws;

import java.util.ArrayList;
import java.util.List;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.WebResult;

@WebService(name   = "CitiesService", 
            serviceName = "CitiesService",
            portName    = "CitiesServicePort")
public class Cities {
    public Cities() {
    }
    
    @WebMethod
    @WebResult(name = "result")
    public List<City> getCities( @WebParam(name = "countryCode" ) String countryCode) {
        List<City> result = new ArrayList<City>();
        if ( countryCode.equalsIgnoreCase("nl") ) {
          City  c = new City();
          c.setCountry("NL");
          c.setName("PUTTEN");
          result.add(c);
          City  c2 = new City();
          c2.setCountry("NL");
          c2.setName("AMSTERDAM");
          result.add(c2);
        } else if ( countryCode.equalsIgnoreCase("de") ) {
            City  c = new City();
            c.setCountry("DE");
            c.setName("BERLIN");
            result.add(c);
            City  c2 = new City();
            c2.setCountry("DE");
            c2.setName("FRANKFURT");
            result.add(c2);
        } else {
            City  c = new City();
            c.setCountry(countryCode);
            c.setName("NOT FOUND");
            result.add(c);
        }
        return result;
    }
}

package nl.whitehorses.ws;

public class City {

    private String name;
    private String country;

    public City() {
    }

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

    public String getName() {
        return name;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return country;
    }
}
Now it is time to do some work in Flash Builder 4. Go to the Data menu and select in this case Connect to Web Service. This works the same for a Remote Object

Select Through a LCDS/BlazeDS proxy destination and select your WS destination. Configured in the proxy-config.xml

Press finish and this will generate the client side code of this WS in your FB4 project.
Go to the Data / Services window and select the getCities WS Operation and press Generate Service Call

This will generate some code in the mxml to call this getCities Operation and handle the result.
Add a datagrid and Bind this to the GetCities Data provider.
Use the Existing call result.
FB4 will now change the DataGrid with the right columns.
That was the hard part and now you can finish this by Adding a Combobox to provide the input for a getCities operation.
<?xml version="1.0" encoding="utf-8"?>
<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:wscities="services.wscities.*"> 

 <s:applicationComplete>
  <![CDATA[
      initApp();
  ]]>
 </s:applicationComplete>
 
 <fx:Script>
  <![CDATA[
   import mx.controls.Alert;

   public function initApp():void 
   { 
    getCities(country.selectedItem.data);
   } 
   
   protected function getCities(countryCode:String):void
   {
    getCitiesResult.token = wsCities.getCities(countryCode);
   }

   private function changeComboBoxEvt(e:Event):void {
    getCities(e.currentTarget.selectedItem.data);
   }
   
  ]]>
 </fx:Script>

 <fx:Declarations>
  <s:CallResponder id="getCitiesResult"/>
  <wscities:WsCities id="wsCities" fault="Alert.show(event.fault.faultString +
         '\n' + event.fault.faultDetail)" showBusyCursor="true"/>
 </fx:Declarations>

 <s:Panel title="RDC with FB4 &amp; BlazeDS">
  <s:VGroup>
   <mx:ComboBox id="country" width="150" change="changeComboBoxEvt(event)">
    <mx:ArrayList>
     <fx:Object label="Netherlands" data="NL"/>
     <fx:Object label="Germany" data="DE"/>
    </mx:ArrayList>
   </mx:ComboBox>
   
   <mx:DataGrid x="53" y="36" id="dataGrid" dataProvider="{getCitiesResult.lastResult}">
    <mx:columns>
     <mx:DataGridColumn headerText="country" dataField="country"/>
     <mx:DataGridColumn headerText="name" dataField="name"/>
    </mx:columns>
   </mx:DataGrid>
  </s:VGroup>
 </s:Panel>
</s:Application>
If you don't like these mouse clicks you can always do it manually in actionscript.
<?xml version="1.0" encoding="utf-8"?>
<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:wscities="services.wscities.*"> 

 
 <s:applicationComplete>
  <![CDATA[
  initApp();
  ]]>
 </s:applicationComplete>
 
 <fx:Script>
  <![CDATA[
   import mx.controls.Alert;
   import mx.events.FlexEvent;
   import mx.rpc.AsyncToken;
   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;
   
   import services.wscities.WsCities;
   
   public var ws:WsCities; 
   
   public function initApp():void 
   { 
    ws = new WsCities(); 
    var token:AsyncToken = ws.getCities(country.selectedItem.data);
    token.addResponder(new mx.rpc.Responder(onCitiesResult, onCitiesFault));
   } 
   
   private function onCitiesResult(e:ResultEvent):void {
    dataGrid.dataProvider=e.result;
   }
   
   private function onCitiesFault(e:FaultEvent):void {
    Alert.show(e.fault.faultString, "Fault");
   }
   
   private function changeComboBoxEvt(e:Event):void {
    var token:AsyncToken = ws.getCities(e.currentTarget.selectedItem.data);
    token.addResponder(new mx.rpc.Responder(onCitiesResult, onCitiesFault));
    
    
   }
  ]]>
 </fx:Script>
 
 <s:Panel title="RDC with FB4 &amp; BlazeDS">
  <s:VGroup>
   <mx:ComboBox id="country" width="150" change="changeComboBoxEvt(event)">
    <mx:ArrayList>
     <fx:Object label="Netherlands" data="NL"/>
     <fx:Object label="Germany" data="DE"/>
    </mx:ArrayList>
   </mx:ComboBox>
   
   <mx:DataGrid x="53" y="36" id="dataGrid" >
    <mx:columns>
     <mx:DataGridColumn headerText="country" dataField="country"/>
     <mx:DataGridColumn headerText="name" dataField="name"/>
    </mx:columns>
   </mx:DataGrid>
  </s:VGroup>
 </s:Panel> 
</s:Application> 

Saturday, January 23, 2010

Flex data push with BlazeDS and EJB3

With BlazeDS we can push data from the J2EE container to the Flex clients just like Adobe Life Cycle Data Services can. In blog I will use the Message Broker and Service Adapter of BlazeDS in combination with EJB3 Entity / Session Bean to push every change to the client. The Message Broker routes the messages with the EJB entities to the custom service adapter. The service Adapter is a service, which registers all the subscribed Flex clients and publish the changes to these client.

For this example I used JDeveloper 11g and Weblogic. ( For more information see my previous blog ) .
This are the steps we need to do to make this work.
  • Generate an Entity Bean with Entity from Tables in JDeveloper
  • Generate an EJB Session Bean and Add the Message Broker code
  • Make an ServiceAdapter
  • Configure the BlazeDS files
  • Add an Flex Class which maps to the Entity Bean
  • Add the Flex producer and consumer code
  • Create an EJB client for the changes.
We start by creating an Employee Entity Bean. ( Based on the employees table of the Oracle HR schema )

package nl.whitehorses.model.entities;

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;


@Entity
@NamedQueries( { @NamedQuery(name = "Employees.findAll", query = "select o from Employees o") })
public class Employees implements Serializable {
@Column(name="COMMISSION_PCT")
private Double commissionPct;
@Column(name="DEPARTMENT_ID")
private Long departmentId;
@Column(nullable = false, unique = true, length = 25)
private String email;
@Id
@Column(name="EMPLOYEE_ID", nullable = false)
private Long employeeId;
@Column(name="FIRST_NAME", length = 20)
private String firstName;
@Column(name="HIRE_DATE", nullable = false)
private Timestamp hireDate;
@Column(name="JOB_ID", nullable = false, length = 10)
private String jobId;
@Column(name="LAST_NAME", nullable = false, length = 25)
private String lastName;
@Column(name="PHONE_NUMBER", length = 20)
private String phoneNumber;
private Double salary;


public Employees() {
}

.......
}


My HRSessionEJBBean Session Bean, this Bean has a remote interface. JDeveloper can generate this Session Bean for you with its remote interface. This Bean contains the Message Broker code which routes the messages with all the employees when the persist, merge or remove method of the EntityManager is called.

package nl.whitehorses.model.services;

import java.util.List;

import javax.ejb.Remote;
import javax.ejb.Stateless;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import nl.whitehorses.model.entities.Employees;

import flex.messaging.MessageBroker;
import flex.messaging.messages.AsyncMessage;
import flex.messaging.util.UUIDUtils;

@Stateless(name = "HRSessionEJB", mappedName = "flex_ejb2-Model-HRSessionEJB")
@Remote
public class HRSessionEJBBean implements HRSessionEJB {
@PersistenceContext(unitName="Model")
private EntityManager em;

public HRSessionEJBBean() {
}

private void pushEmployees() {
String clientId = UUIDUtils.createUUID();
MessageBroker msgBroker = MessageBroker.getMessageBroker(null);
AsyncMessage msg = new AsyncMessage();
msg.setDestination("EmployeesServicePush");
msg.setClientId(clientId);
msg.setMessageId(UUIDUtils.createUUID());
msg.setBody(getEmployeesFindAll());
msgBroker.routeMessageToService(msg,null);
}

public Employees persistEmployees(Employees employees) {
em.persist(employees);
pushEmployees();
return employees;
}

public Employees mergeEmployees(Employees employees) {
Employees emp = em.merge(employees);
pushEmployees();
return emp;
}

public void removeEmployees(Employees employees) {
employees = em.find(Employees.class, employees.getEmployeeId());
em.remove(employees);
pushEmployees();
}

/** <code>select o from Employees o</code> */
public List<Employees> getEmployeesFindAll() {
System.out.println("getEmps");
return em.createNamedQuery("Employees.findAll").getResultList();
}
}

Create the Blaze Custom Service Adapter, we will add this adapter later to the BlazeDS configuration files. This adapter will do a JNDI lookup of our EJB Session Bean and pushes the messages to the connected Flex clients.

package nl.whitehorses.blazeds.adapter;

import java.util.List;

import flex.messaging.messages.AsyncMessage;
import flex.messaging.messages.Message;
import flex.messaging.services.MessageService;
import flex.messaging.services.ServiceAdapter;


import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import nl.whitehorses.model.entities.Employees;
import nl.whitehorses.model.services.HRSessionEJB;

public class EmployeesServiceAdapter extends ServiceAdapter {
Context context = null;
HRSessionEJB hRSessionEJB = null;

public EmployeesServiceAdapter() {
try {
context = new InitialContext();
hRSessionEJB =
(HRSessionEJB)context.lookup("flex_ejb2-Model-HRSessionEJB#nl.whitehorses.model.services.HRSessionEJB");
} catch (NamingException e) {
e.printStackTrace();
}
System.out.println("Adapter initilized");
}

public void start() {
System.out.println("Adapter started");

}

public void stop() {
System.out.println("Adapter stopped");
}

private List<Employees> getEmployees() {
return hRSessionEJB.getEmployeesFindAll();
}

public Object invoke(Message msg) {
if (msg.getBody().equals("New")) {
System.out.println("Adapter received new");
return getEmployees();
} else {
System.out.println("Adapter sending message");
AsyncMessage newMessage = (AsyncMessage)msg;
MessageService msgService =
(MessageService)getDestination().getService();
msgService.pushMessageToClients(newMessage, true);
}
return null;
}
}

In the messaging-config.xml we need add this custom Service Adapter and add a Employee destination which connect this to our custom adapter and the streaming AMF channel

<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service" class="flex.messaging.services.MessageService">

<adapters>
<adapter-definition id="EmployeesServicePushAdapter" class="nl.whitehorses.blazeds.adapter.EmployeesServiceAdapter"/>
</adapters>

<destination id="EmployeesServicePush">
<channels>
<channel ref="my-streaming-amf" />
</channels>
<adapter ref="EmployeesServicePushAdapter"/>
</destination>

</service>

The services-config.xml which import the messaging-config.xml configuration file and contains the streaming-amf channel configuration.

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service-include file-path="messaging-config.xml" />
<default-channels>
<channel ref="my-amf"/>
</default-channels>
</services>

<channels>
<channel-definition id="my-amf"
class="mx.messaging.channels.AMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
</properties>
</channel-definition>

<channel-definition id="my-streaming-amf"
class="mx.messaging.channels.StreamingAMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/streamingamf"
class="flex.messaging.endpoints.StreamingAMFEndpoint"/>
<properties>
<idle-timeout-minutes>0</idle-timeout-minutes>
<max-streaming-clients>10</max-streaming-clients>
<server-to-client-heartbeat-millis>5000</server-to-client-heartbeat-millis>
<user-agent-settings>
<user-agent match-on="MSIE" kickstart-bytes="2048" max-streaming-connections-per-session="3"/>
<user-agent match-on="Firefox" kickstart-bytes="2048" max-streaming-connections-per-session="3"/>
</user-agent-settings>
</properties>
</channel-definition>
</channels>

<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Error">
<properties>
<prefix>[Flex]</prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
</target>
</logging>
</services-config>

We are finished with the java part and we can work on the Flex part.
In Flex we need to add a simple Employees class which is connected to the entity bean

package entities
{
[Bindable]
[RemoteClass(alias="nl.whitehorses.model.entities.Employees")]
public class Employees
{
public var commissionPct:Number;
public var departmentId:int;
public var email:String;
public var employeeId:int;
public var firstName:String;
public var hireDate:Date;
public var jobId:int;
public var lastName:String;
public var phoneNumber:String;
public var salary:Number;


public function Employees()
{
}

}
}

The Flex application mxml with the producer and consumer component

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="1200" height="500">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.messaging.messages.IMessage;
import mx.messaging.events.MessageAckEvent;
import mx.messaging.messages.AsyncMessage;
import mx.messaging.events.MessageEvent;
import entities.Employees;

private function init():void{
var message:AsyncMessage = new AsyncMessage();
message.body = "New";
producer.send(message);
consumer.subscribe();
}

private function onMsg(event:MessageEvent):void{
grid.dataProvider = event.message.body as ArrayCollection;
}

private function pub():void {
var message:AsyncMessage = new AsyncMessage();
message.body = "New";
producer.send(message);
}

private function ack(event:MessageAckEvent):void{
grid.dataProvider = event.message.body as ArrayCollection;
}

]]>
</mx:Script>
<mx:applicationComplete>init();</mx:applicationComplete>

<mx:Producer id="producer" destination="EmployeesServicePush" acknowledge="ack(event)"/>
<mx:Consumer id="consumer" destination="EmployeesServicePush" message="onMsg(event)"/>

<mx:VBox>
<mx:DataGrid id="grid">
<mx:columns>
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
<mx:DataGridColumn dataField="departmentId" headerText="Department"/>
</mx:columns>
</mx:DataGrid>
</mx:VBox>



</mx:Application>

We can start the J2EE container and the Flex client. To test this we can use an EJB test client which adds a new Employee and look in the Flex application if we can see this new Employee.

package test;

import java.util.Calendar;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;

import javax.naming.NamingException;

import nl.whitehorses.model.entities.Employees;
import nl.whitehorses.model.services.HRSessionEJB;

public class HRSessionEJBClient {
public static void main(String [] args) {
try {
final Context context = getInitialContext();
HRSessionEJB hRSessionEJB = (HRSessionEJB)context.lookup("flex_ejb2-Model-HRSessionEJB#nl.whitehorses.model.services.HRSessionEJB");

Employees emp = new Employees();
emp.setEmployeeId(995L);
emp.setDepartmentId(50L);
emp.setFirstName("a");
emp.setLastName("a");
emp.setJobId("SH_CLERK");
emp.setEmail("a");
emp.setHireDate(new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()));
hRSessionEJB.mergeEmployees(emp);

} catch (Exception ex) {
ex.printStackTrace();
}
}

private static Context getInitialContext() throws NamingException {
Hashtable env = new Hashtable();
// WebLogic Server 10.x connection details
env.put( Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory" );
env.put(Context.PROVIDER_URL, "t3://127.0.0.1:7101");
return new InitialContext( env );
}
}

Friday, January 15, 2010

Adobe Flex in Weblogic with BlazeDS & EJB3

In a earlier post I already made Adobe Flex / BlazeDS / EJB2.1 example and this example was deployed on an OC4J J2EE container. In this post I let the same example run in Oracle Weblogic 10.3.2 ( FMW11g) with the BlazeDS jars as a shared library ( this also works with Adobe Lifecycle). Also in the earlier post I needed to use EJB2.1 but with EJB factory of Peter Martin I can use Eclipselink.
To start, we need to download BlazeDS and EJB and Flex Integration jar, Extract these archives so we can copy the jar files to a new location.

We need to make a shared library for weblogic. This is better then just add all the jars to WEB-INF/lib folder of every web application. You can patch the blazeds jars without re-deploying your webapps and you now know exactly which version of blazeds you are using.
Make a folder called blazeds with as subfolders APP-INF and META-INF and add a empty zip in it and call it empty.jar

Copy the blazeds and ejb factory jars in the APP-INF/lib folder

In the META-INF folder we need to add two files application.xml and MANIFEST.MF where we will add the library information for weblogic.

The application.xml

<?xml version = '1.0' encoding="UTF-8" ?>
<application xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="1.4">
<description>BlazeDS Library</description>
<display-name>blazeds</display-name>
<module>
<java>empty.jar</java>
</module>
</application>

The MANIFEST.MF has this content
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0RC1
Created-By: 14.0-b16 (Sun Microsystems Inc.)
Extension-Name: blazeds
Specification-Version: 3.2
Implementation-Title: BlazeDS - BlazeDS Application
Implementation-Version: 3.2.0.3978
Implementation-Vendor: Adobe Systems Inc.


We are ready to add this library to the Weblogic Server. The library folder is on the same file system as the weblogic server so I don't need to make an EAR file, I can just use the exploded folder. Open the Weblogic Console and go to Deployments. Here we can install a new Deployment.

Go to blazeds library folder
Install it as a libray

Weblogic will detect the manifest and application.xml and press Finish


In the deployment page we can see the blazeds library.
In the ViewController project we need to make a reference to this shared libray, to do this add a weblogic deployment descriptor. We can use weblogic-application.xml located in the META-INF folder of the EAR or use weblogic.xml in the WEB-INF of the Viewcontroller project. Here is an example of my weblogic-application.xml

<?xml version = '1.0' encoding = 'windows-1252'?>
<weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-application http://www.bea.com/ns/weblogic/weblogic-application/1.0/weblogic-application.xsd"
xmlns="http://www.bea.com/ns/weblogic/weblogic-application">
<library-ref>
<library-name>blazeds</library-name>
</library-ref>
</weblogic-application>

The last steps is to configure the web.xml for blazeds and the blazeds configuration files for the EJB call.
first the web.xml of the viewcontroller project.

<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app 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/web-app_2_5.xsd"
version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
<description>Empty web.xml file for Web Application</description>

<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/flex/services-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

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

<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>


<mime-mapping>
<extension>html</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<mime-mapping>
<extension>txt</extension>
<mime-type>text/plain</mime-type>
</mime-mapping>
</web-app>

I use the remoting-config.xml and services-config.xml files both located in WEB-INF/flex folder
Here is my remoting-config.xml file, where source element is the JNDI name of my EJB Session Bean, after deployed of your model you can look it up in the Weblogic console ( servers, select the server and in the top there is a link to the jndi page )

<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service" class="flex.messaging.services.RemotingService">
<adapters>
<adapter-definition id="java-object"
class="flex.messaging.services.remoting.adapters.JavaAdapter"
default="true"/>
</adapters>
<default-channels>
<channel ref="my-amf"/>
</default-channels>
<destination id="EmployeeEJB">
<properties>
<factory>ejb3</factory>
<source>flex_ejb2-Model-HRSessionEJB#nl.whitehorses.model.services.HRSessionEJB</source>
</properties>
</destination>
</service>

my services-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service-include file-path="remoting-config.xml"/>
<default-channels>
<channel ref="my-amf"/>
</default-channels>
</services>
<factories>
<factory id="ejb3" class="com.adobe.ac.ejb.EJB3Factory"/>
</factories>
<channels>
<channel-definition id="my-amf"
class="mx.messaging.channels.AMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
</properties>
</channel-definition>
</channels>
<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Error">
<properties>
<prefix>[Flex]</prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
</target>
</logging>
</services-config>

Deploy everything to the Weblogic server.
and at last the Adobe mxml where I call the getEmployeesFindAll method.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="1155" height="206.21213">
<mx:applicationComplete>srv.getEmployeesFindAll()</mx:applicationComplete>

<mx:RemoteObject id="srv" showBusyCursor="true" destination="EmployeeEJB"/>
<mx:DataGrid width="1104.6212" dataProvider="{srv.getEmployeesFindAll.lastResult}" height="140.98485"/>

</mx:Application>

Friday, December 25, 2009

Flex embedded youtube player 1.2

I made a new version of the Flex embedded YouTube player ( For all features see the 1.1 version ). This time I use javascript with ExternalInterface instead of a php proxy script and this version will work forever because it all runs on the client side. Since april 2009 the proxy way won't work anymore. This version is based on the javascript & actionscript code of Matthew Richmond. Click here for his demo and source code.

First we need to change video.xml, url is now only the youtube id

<node id="0" category="overzicht">
<node id="0" category="2008">
<node id="0" category="Europees">
<node id="2012" category="Bauska 2008" url="c9BqEm7zxU4"/>
<node id="2011" category="Bauska 2008 2" url="BwfPfDAMnF0"/>
</node>
</node>
<node id="0" category="2007">
<node id="0" category="Europees">
<node id="421" category="Autocross Championship - Murça 1" url="8TZgXfqpd5I"/>
<node id="420" category="Autocross Championship - Murça 2" url="6Ncyu7-7UMA"/>
<node id="419" category="Autocross Murça 2007: Div.3 Campeonato" url="NLF2BGQmFrY"/>
<node id="418" category="Autocross race in Hungary" url="21wjz-tGcC4"/>
</node>
</node>
</node>

And we need to add some javascript to the html page. Here is my Adobe Flex project. Happy tubing.

Monday, November 17, 2008

Tour de Flex, a great Flex example library

Greg Wilson, Christophe Coenraets and James Ward have made an Adobe Air application called Tour de Flex. This app includes now 217 runnable flex samples, each with source code, links to documentation, and other details. Check this out go this url and download the 50mb air application. Now you always have your flex examples library with you.

Here a overview of the mapping examples ( google & yahoo maps) with the source code

The Tour de Flex topics

A ebay example.

This air application is a must have for every flex developer. Great Job ria cowboys.

Sunday, September 28, 2008

Flex Ruby performance with XML, JSON and RubyAMF

One of the great things when you use Flex in combination Ruby is that you can easily change the communication method. For instance you can use xml rpc or json rpc or RubyAMF, just by changing the ruby controller. Off course when you use json then you need to download the corelib for Flex and when you use RubyAMF we need to install this plugin in ruby.
But when you have a choice then you need to know what is the best communication method for your project. In this test I will measure the performance of the three methods. For this I made a simple Flex / Ruby project.

Here are the average results in ms (100 times executed) with the total records count in our test table.
records101004001000
amf159213380731
json4575252781
xml4357197612

Conclusion
RubyAMF has a little overhead and is fast with a large recordset and you can use remoteobject in Flex. Xml is fast but the performance can vary. With 1000 records it can take 200ms but sometimes 2000ms. It looks like Ruby can cache xml and sometimes refresh the cache. Json is very stable and fast with small recordsets. If you just want to retrieve some data in Flex, I would use json but when you want to do more with this data in Flex then RubyAMF has many benefits, like association between objects, conversion to actionscript class and a lot more.

The Ruby Code
you can test the output by adding the format parameter to the url http://localhost:3000/departments/find_all?format=json or format=xml.
Here is the controller code I used

class DepartmentsController < ApplicationController

# return all Departments
def find_all
respond_to do |format|
format.amf { render :amf => Department.find(:all) }
format.json { render :text => Department.find(:all).to_json }
format.xml { render :xml => Department.find(:all) }
end
end

end

department table created in a mysql database

class CreateDepartments < ActiveRecord::Migration

def self.up
create_table :departments do |t|
t.string :name
t.string :location
t.timestamps
end
end

def self.down
drop_table :departments
end
end

RubyAMF configuration

require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','updated_at']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false

ClassMappings.register(
:actionscript => 'Department',
:ruby => 'Department',
:type => 'active_record',
:attributes => ["id", "name", "location", "created_at", "updated_at"])

ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = true
ClassMappings.check_for_associations = true
ParameterMappings.always_add_to_params = true
end
end


The Flex code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import vo.Timing;

private var idNum:int;
private var maxNum:int = 100;

[Bindable]
private var timing:ArrayCollection = new ArrayCollection();

private function startRubyAMF():void {
timing = new ArrayCollection();
idNum = 0;
loadAll();
}
private function startJSON():void {
timing = new ArrayCollection();
idNum = 0;
loadAllJson();
}
private function startXML():void {
timing = new ArrayCollection();
idNum = 0;
loadAllXML();
}
]]>
</mx:Script>


<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import mx.collections.ArrayCollection;

[Bindable]
private var departments:ArrayCollection = new ArrayCollection();
private var rubyDateFrom:Date;

private function loadAll():void {
rubyDateFrom = new Date();
var token:AsyncToken = AsyncToken(departmentService.find_all());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyDateFrom;
timing.addItem(time);

}

private function faultHandler(event:FaultEvent):void {
Alert.show(event.fault.faultString + " : " + event.fault.faultCode + " : " + event.fault.faultDetail , "Error in LoginCommand");
}

private function resultHandler(event:ResultEvent):void {
departments = event.result as ArrayCollection;
var rubyDateFinish:Number = new Date().valueOf() - rubyDateFrom.valueOf() ;
rubyLabel.text="RubyAMF departments "+rubyDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.rubyamf = rubyDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAll();
} else {
var average:Number = 0;
for ( var i:int = 0 ; i < timing.length ; i++ ) {
var time2:Timing = timing.getItemAt(i) as Timing;
average = average + time2.rubyamf;
}
average = average / timing.length;
rubyLabel.text="RubyAMF departments average "+average.toString()+" ms";

trace("end");
}
}

]]>
</mx:Script>
<mx:HBox>
<mx:Button label="refresh RubyAMF" click="startRubyAMF()"/>
<mx:Button label="refresh JSON" click="startJSON()"/>
<mx:Button label="refresh XML" click="startXML()"/>
</mx:HBox>

<mx:RemoteObject id="departmentService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="DepartmentsController"
showBusyCursor="true"
result="resultHandler(event)"
fault="faultHandler(event)"
/>

<mx:Label id="rubyLabel" text="RubyAMF departments"/>

<mx:DataGrid id="dg" dataProvider="{departments}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:Script>
<![CDATA[
import com.adobe.serialization.json.JSON;
[Bindable]
private var dp:ArrayCollection = new ArrayCollection() ;
private var rubyJsonDateFrom:Date;

private function loadAllJson():void {
rubyJsonDateFrom = new Date();
var token:AsyncToken = AsyncToken(json.send());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyJsonDateFrom;
timing.addItem(time);
}


private function resultHandlerJSON(event:ResultEvent):void
{
dp = new ArrayCollection();
var rawData:String = String(event.result);
var arr:Array = (JSON.decode(rawData) as Array);
for ( var i:int = 0 ; i < arr.length ; i++ ) {
dp.addItem(arr[i].department);
}
var rubyJsonDateFinish:Number = new Date().valueOf() - rubyJsonDateFrom.valueOf() ;
rubyJSONLabel.text="JSON departments "+rubyJsonDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.json = rubyJsonDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAllJson();
} else {
var average:Number = 0;
for ( var ii:int = 0 ; ii < timing.length ; ii++ ) {
var time2:Timing = timing.getItemAt(ii) as Timing;
average = average + time2.json;
}
average = average / timing.length;
rubyJSONLabel.text="JSON departments average "+average.toString()+" ms";

trace("end");
}


}
]]>
</mx:Script>

<mx:HTTPService id="json"
url="http://localhost:3000/departments/find_all?format=json"
result="resultHandlerJSON(event)" useProxy="false" />
<mx:Label id="rubyJSONLabel" text="JSON departments"/>
<mx:DataGrid id="dg4" dataProvider="{dp}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:Script>
<![CDATA[
import com.adobe.serialization.json.JSON;
[Bindable]
private var dp2:ArrayCollection = new ArrayCollection() ;
private var rubyXmlDateFrom:Date;

private function loadAllXML():void {
rubyXmlDateFrom = new Date();
var token:AsyncToken = AsyncToken(xml.send());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyJsonDateFrom;
timing.addItem(time);


}

private function resultHandlerXML(event:ResultEvent):void
{
if ( event.result != null ) {
dp2 = event.result.departments.department;
}
var rubyXmlDateFinish:Number = new Date().valueOf() - rubyXmlDateFrom.valueOf() ;
rubyXmlLabel.text="XML departments "+rubyXmlDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.xml = rubyXmlDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAllXML();
} else {
var average:Number = 0;
for ( var ii:int = 0 ; ii < timing.length ; ii++ ) {
var time2:Timing = timing.getItemAt(ii) as Timing;
average = average + time2.xml;
}
average = average / timing.length;
rubyXmlLabel.text="XML departments average "+average.toString()+" ms";
trace("end");
}
}
]]>
</mx:Script>


<mx:HTTPService id="xml" url="http://localhost:3000/departments/find_all?format=xml"
result="resultHandlerXML(event)" useProxy="false" />
<mx:Label id="rubyXmlLabel" text="XML departments"/>
<mx:DataGrid id="dg5" dataProvider="{dp2}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

</mx:Application>

Tuesday, September 9, 2008

Using Exadel Fiji for Flex in JDeveloper 11g

With Exadel Fiji you can use Flex in a JSF page. For more infomations see my previous blog. This blog will show you how you use fiji & flex in JDeveloper 11g. This will not work in jdeveloper 10.1.3.
The first step is to download Fiji from http://exadel.com. To make this work in JDeveloper, we need to disable the trinidad code and configure Exadel Richfaces and Ajax4jsf. We need to add the fiji taglib to the project. To do this, Go to the project options / jsp tag libraries and press Add, click user and press the New button. Now we can select the fiji-ui-1.0.0.jar. We need to remove all other taglibs except jsf-core and jsf-html.

Now we can add the right libraries to the project. We can use the following libraries of JDeveloper 11G Facelets runtime, commons-logging, common-digester and common-beanutils. The rest of libraries comes from the fiji lib folder. See the picture for all the used fiji libs. Don't use the commons-digester of JDeveloper. Use version 1.8 of fiji download.


This is how the web.xml look like. You can better make this file readonly else JDeveloper 11g will try to add the Trinidad filters.

<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app 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/web-app_2_5.xsd"
version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
<description>Empty web.xml file for Web Application</description>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<filter>
<display-name>Ajax4jsf Filter</display-name>
<filter-name>ajax4jsf</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
<init-param>
<param-name>createTempFiles</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>maxRequestSize</param-name>
<param-value>100000</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ajax4jsf</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>35</session-timeout>
</session-config>
</web-app>


We had to remove the oracle default render kit from the faces-config.xml and add FaceletViewHandler for Ajax4jsf. Make this file readonly else JDeveloper will try to add the default render kit.

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
<managed-bean>
<managed-bean-name>Test</managed-bean-name>
<managed-bean-class>nl.ordina.fiji.backing.TestBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>


The backing bean code with the retrieveWelcomeText method which I use in the remote object (Flex).

package nl.ordina.fiji.backing;
import java.util.Date;

public class TestBean {
public TestBean() {
}

private String welcomeText = "Hello Flex in JSF";


public void setWelcomeText(String welcomeText) {
this.welcomeText = welcomeText;
}

public String getWelcomeText() {
return welcomeText;
}

public String retrieveWelcomeText(Object obj) {
this.welcomeText = "Hello Flex in JSF" + new Date().toString();
return this.welcomeText;
}

}


jsf xhtml page with the fiji:swf component. This element has two sub elements the first is a simple param component which pass its values to flex as a parameter. The second component is a endpoint and this is used by the remoteobject.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
xmlns:fiji="http://exadel.com/fiji">

<ui:composition>
<p>
<h:form></h:form>
<fiji:swf src="/Flex_fiji.swf" id="simpleFlex"
bgcolor="#FFFFFF" width="450" height="250" >
<f:param name="welcomeText" value="#{Test.welcomeText}" />
<fiji:endpoint name="endpoint2" binary="true" service="#{Test.retrieveWelcomeText}"/>
</fiji:swf>
</p>
</ui:composition>
</html>


Here is the flex code where we use Application.application.parameters to retrieve the parameter value or a reference url for the remoteobject.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="onCreationComplete();">

<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;

private function refresh():void {
myService.getWelcomeObject();
}

public function handleResult(event:ResultEvent):void {
welcome.text = event.result as String;
}

public function onCreationComplete():void{
Alert.show(Application.application.parameters.endpoint2);
myService.endpoint = Application.application.parameters.endpoint2;
}
]]>
</mx:Script>
<mx:RemoteObject id="myService" destination="bean"
result="handleResult(event)" />
<mx:Form x="22" y="10" width="280">
<mx:TextInput id="welcome" styleName="text"
text="{Application.application.parameters.welcomeText}" />
<mx:Button label="Refresh"
click="refresh()"/>
</mx:Form>
</mx:Application>

That's all. Here you can download the JDeveloper 11g project.

Saturday, September 6, 2008

Use Flex in JSF with Exadel Fiji

Maybe you already saw this news at the serverside or by James Ward but Exadel just released Fiji. With Fiji you can use Flex applications in a JSF page and interact with the other JSF component or backing beans. I already tried this too but Exadel got it working with a lot of great options. You can develop now better looking and richer JSF applications with cool video or charts components. These flex applications can be a part of the jsf application without using blazeds or lifecycle. And of course you can use Flex to build complex parts of your jsf page ( like Charts, Drag and Drop, Video or Trees) and you can do it a lot faster and it is easier.

Here can you see the exadel jsf flex demos or read more about Fiji at the Exadel product page .

Fiji options
1) use f:param to pass values to the flex application
2) use HTTPService to retrieve any value of a backing bean method.
3) use DataService to retrieve the result using AMF format.
4) Invoking Ajax Request to send events to other parts of the jsf page.
5) Access to the flex api from the jsf page
6) New Flex Charts components in JSF without making a flex application

A little example
You can pass parameters to the flex application. Just use f:param and el

<fiji:swf src="/simpleHello/simpleHello.swf" id="simpleHello"
bgcolor="#FFFFFF" width="320" height="180">
<f:param name="userName" value="#{bean.simpleHelloUserName}" />
</fiji:swf>

And retrieve the parameters in Flex just use Application.application.parameters.userName

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Form x="22" y="10" width="280">
<mx:Label id="userName" styleName="text" text="{Application.application.parameters.userName}" />
</mx:Form>
</mx:Application>

Wednesday, September 3, 2008

Google Chrome with ADF 10G & 11G, Applet and Flash

Google released their new browser called Chrome, which is off course still beta ( that is normal for google). This browser not only looks fancy but it is very nice to browse the web. Andrej Koelewijn already talked about the internal memory page. Let's try some sites to know what the normal memory usage is. To do this you can open the taskmanager

This gives you already a nice overview.

or you can type about:memory in the url input. This gives you more memory info. So let's test some websites ( like cnn.com and my own blog), an ADF 10.1.3 , ADF 11g application, an applet and a flash application. Here are the results.

A normal website ( Tab 2 and 5 ) use about 26 MB of memory, the most complex 10g ADF page ( Tab 9) use 22MB and ADF 11g website gives me a not supported notice. Chrome also shows flash as a different memory process. A big flex application can take 50MB of memory and a small one 22MB.

Too bad I can't not run a java applet with the current installed jre. I need to install Java SE 6 Update 10 RC beta. You can download this at http://java.sun.com.
A simple Applet takes about 29MB of memory.

Happy browsing.

Monday, September 1, 2008

Associations with Ruby on Rails, Flex and RubyAMF

In this blog item I will you show how easy it is with RoR and RubyAMF to do CRUD operations on tables with associations. RoR and RubyAMF makes it very easy. I used for this blog a simple department and their employees use case.
Here is an picture of the result. A department has employees and an employee belongs to a department

First step is to create the RoR application
rails hr_app

change directory to the new application
cd hr_app

We need to edit the database configuration file
hr_app\config\database.yml

Install RubyAMF
ruby script/plugin install http://rubyamf.googlecode.com/svn/tags/current/rubyamf

Generate the department and employee table
ruby script/generate rubyamf_scaffold department
ruby script/generate rubyamf_scaffold employee


change department table configuration in hr_app/db/migrate folder

class CreateDepartments < ActiveRecord::Migration

def self.up
create_table :departments do |t|
t.string :name
t.string :location
t.timestamps
end
end

def self.down
drop_table :departments
end
end


change employee table also located in the hr_app/db/migrate folder

class CreateEmployees < ActiveRecord::Migration

def self.up
create_table :employees do |t|
t.integer :department_id
t.string :first_name
t.string :last_name
t.string :job
t.timestamps
end
end

def self.down
drop_table :employees
end
end

Let's create the department and employee table
rake db:migrate

We can add one to many associations in RoR
Edit hr_app/app/models/department.rb file where we will add has_many

class Department < ActiveRecord::Base
has_many :employees
end

Edit hr_app/app/models/employee.rb file where we will add belongs_to

class Employee < ActiveRecord::Base
belongs_to :department
end

The RoR configuration is ready we only have to configure RubyAMF

We can generate the class mappings and copy this to the rubyamf_config.rb configuration
ruby script/generate rubyamf_mappings

The generator detects the associations between department and employees
Copy the generated block of text into config/rubyamf_config.rb:
edit hr_app/config/rubyamf_config.rb

require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','updated_at']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false

ClassMappings.register(
:actionscript => 'Department',
:ruby => 'Department',
:type => 'active_record',
:associations => ["employees"],
:attributes => ["id", "name", "location", "created_at", "updated_at"])

ClassMappings.register(
:actionscript => 'Employee',
:ruby => 'Employee',
:type => 'active_record',
:associations => ["department"],
:attributes => ["id", "first_name", "last_name", "job", "created_at", "updated_at", "department_id"])

ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = true
ClassMappings.check_for_associations = true
ParameterMappings.always_add_to_params = true
end
end

Now we can start the RoR application
ruby script/server

Create a new Flex application.
First we will create the employee and department actionscript class.
The employee field in the Department class is an arraycollection

package vo
{
import mx.collections.ArrayCollection;


[RemoteClass(alias="Department")]
[Bindable]
public class Department
{
public var id:int;
public var name:String;
public var location:String;
public var createdAt:Date;
public var updatedAt:Date;
public var employees:ArrayCollection;
public function Department()
{
}

}
}

In the Employee class we add the department field of type Department

package vo
{
[RemoteClass(alias="Employee")]
[Bindable]
public class Employee
{
public var id:int;
public var firstName:String;
public var lastName:String;
public var job:String;
public var departmentId:int;
public var createdAt:Date;
public var updatedAt:Date;
public var department:Department;
public function Employee()
{
}
}
}

Here is the mxml code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="loadAll(); loadAll2();">

<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import vo.Department;
import vo.Employee;
import mx.collections.ArrayCollection;

[Bindable]
private var departments:ArrayCollection = new ArrayCollection();
[Bindable]
private var employees:ArrayCollection = new ArrayCollection();


private function loadAll():void {
var token:AsyncToken = AsyncToken(departmentService.find_all());
token.kind = "fill";
}
private function loadAll2():void {
var token:AsyncToken = AsyncToken(employeeService.find_all());
token.kind = "fill";
}

private function createDept():void {
var dept:Department = new Department();
dept.name = "Headquarters";
dept.location = "Putten";
dept.employees = new ArrayCollection;
var token:AsyncToken = AsyncToken(departmentService.save(dept));
token.kind = "create";
}

private function createDeptWithEmp():void {
var dept:Department = new Department();
dept.name = "Headquarters";
dept.location = "Putten";
dept.employees = new ArrayCollection;

var emp:Employee = new Employee;
emp.firstName = "pipo";
emp.lastName = "pipo";
emp.job = "clown"
dept.employees.addItem(emp);

var token:AsyncToken = AsyncToken(departmentService.save(dept));
token.kind = "create";
}

private function destroy():void {
var token:AsyncToken = AsyncToken(departmentService.destroy(dg.selectedItem.id));
token.kind = "delete";
}

private function faultHandler(event:FaultEvent):void {
Alert.show(event.fault.faultString + " : " + event.fault.faultCode + " : " + event.fault.faultDetail , "Error in LoginCommand");
}

private function resultHandler(event:ResultEvent):void {
if ( event.token.kind == "fill" ) {
departments = event.result as ArrayCollection;
} else {
loadAll();
loadAll2();
}
}
private function resultHandler2(event:ResultEvent):void {
if ( event.token.kind == "fill" ) {
employees = event.result as ArrayCollection;
} else {
loadAll2();
}
}
]]>
</mx:Script>


<mx:RemoteObject id="departmentService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="DepartmentsController"
showBusyCursor="true"
result="resultHandler(event)"
fault="faultHandler(event)"
/>

<mx:RemoteObject id="employeeService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="EmployeesController"
showBusyCursor="true"
result="resultHandler2(event)"
fault="faultHandler(event)" />

<mx:HBox>
<mx:VBox>
<mx:Label text="Departments with employees"/>
<mx:HBox>
<mx:Button label="Create Department" click="createDept()"/>
<mx:Button label="Create Department with Employee" click="createDeptWithEmp()"/>
<mx:Button label="Remove Department" click="destroy()"/>
</mx:HBox>
<mx:DataGrid id="dg" dataProvider="{departments}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:DataGrid id="dg_2" dataProvider="{dg.selectedItem.employees}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
</mx:columns>
</mx:DataGrid>

</mx:VBox>
<mx:VBox>

<mx:Label text="Employees"/>

<mx:DataGrid id="dg2" dataProvider="{employees}" variableRowHeight="true">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
<mx:DataGridColumn dataField="department">
<mx:itemRenderer>
<mx:Component>
<mx:VBox>
<mx:Text text="{data.department.name}"/>
<mx:Text text="{data.department.location}"/>
</mx:VBox>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
</mx:VBox>

</mx:HBox>
</mx:Application>

That's all. you can easily create and retrieve departments with it's employees with RoR and RubyAMF. RoR and RubyAMF do all the hard work.

Friday, August 29, 2008

Flex and Ruby on Rails with RubyAMF

My earlier Flex blogs often talks about Flex in combination with java but you don't need to java you can use Ruby on Rails too. There are a lot blog entries how to do this. I already made a little demo application where I use RoR and the HttpService in Flex. This solution is based on REST Web Services. RoR gives xml as output.
In this Blog entry I will use RubyAMF. With RubyAMF I can use RemoteObject, RubyAMF will convert the Ruby datatypes to the Flex datatypes and it is really fast.
If you want more information on RubyAMF just click here or go to the RubyAMF discussion groups

Let's create a new Flex Ruby application where we will use RubyAMF. I will create a small comic entry application.

Create a new RoR application:
rails comics_catalog

Change directory to the new comics_catalog folder:
cd comics_catalog

Edit the database connection file ( comics_catalog\config\database.yml ) with the right connection.

Update the project with RubyAMF:
ruby script/plugin install http://rubyamf.googlecode.com/svn/tags/current/rubyamf
This will download RubyAMF code and configuration from subversion to your project.

Create the comic table and controller
ruby script/generate rubyamf_scaffold comic

Edit the comic table configuration. Go to comics_catalog\db\migrate\ folder and edit the 20080827214400_create_comics.rb file. You will have an other filename because of the timestamp. The script need to look like this.

class CreateComics < ActiveRecord::Migration
def self.up
create_table :comics do |t|
t.string :name
t.string :description
t.float :price
t.timestamps
end
end

def self.down
drop_table :comics
end
end

Let's create the comic table in the database
rake db:migrate

The last step in RoR is to configure RubyAMF. For this we need to go the comics_catalog\config folder where we will edit the rubyamf_config.rb file.

require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','created_on','updated_at','updated_on']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false


ClassMappings.register(:actionscript => 'Comic',
:ruby => 'Comic',
:type => 'active_record',
:attributes => ["id","name","description","price", "created_at", "updated_at"])

ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = false
ClassMappings.check_for_associations = false
ParameterMappings.always_add_to_params = true
end
end

I use ClassMappings.ignore_fields because I want to ignore the automatic created fields Ruby will fill these field automatically.
ClassMappings.translate_case is important if you want to Java naming style. RubyAMF will convert created_at to createdAt.
ClassMappings.assume_types is important if you do your know class mapping. In our case we will create the comic class in Flex too so we don't need this. This will give us a little more performance.
ParameterMappings.scaffolding is handy if you just want to pass an comic object or id to the delete method else you need to use {id:dg.selecteditem.id}
ClassMappings.register(:actionscript => 'Comic'. Don't use the package name just put in the actionscript class name.

Start the Ruby server
ruby script/server

Now we can go to Flex, where we create an new application.

first create a comic actionscript object

package vo
{

[RemoteClass(alias="Comic")]
[Bindable]
public class Comic
{
public var id:int;
public var name:String;
public var description:String;
public var price:Number;
public var createdAt:Date;
public var updatedAt:Date;
public function Comic()
{
}


}
}

And here is the mxml code.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="loadAll2();">

<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import vo.Comic;

[Bindable]
private var comics:Array = new Array();

private function loadAll2():void {
var token:AsyncToken = AsyncToken(comicService.find_all());
token.kind = "fill";
}

private function save2():void {
var comic:Comic = new Comic();
comic.id = dg2.selectedItem.id;
comic.name = Name2.text;
comic.description = Description2.text;
var token:AsyncToken = AsyncToken(comicService.save(comic));
token.kind = "save";

}

private function create2():void {
var comic:Comic = new Comic();
comic.name = Name2.text;
comic.description = Description2.text;
var token:AsyncToken = AsyncToken(comicService.save(comic));
token.kind = "create";
}


private function destroy2():void {
var token:AsyncToken = AsyncToken(comicService.destroy(dg2.selectedItem.id));
token.kind = "delete";
}

private function faultHandler(event:FaultEvent):void {
Alert.show(event.fault.faultString + " : " + event.fault.faultCode + " : " + event.fault.faultDetail , "Error in LoginCommand");
}

private function resultHandler2(event:ResultEvent):void {
if ( event.token.kind == "fill" ) {
comics = event.result as Array;
} else {
loadAll2();
}
}



]]>
</mx:Script>
<mx:RemoteObject id="comicService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="ComicsController"
showBusyCursor="true"
result="resultHandler2(event)"
fault="faultHandler(event)" />

<mx:ApplicationControlBar>
<mx:Button label="Create" click="create2()"/>
<mx:Button label="Update" click="save2()"/>
<mx:Button label="Delete" click="destroy2()"/>
<mx:Button label="Refresh" click="loadAll2()"/>
</mx:ApplicationControlBar>

<mx:DataGrid id="dg2" dataProvider="{comics}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="description" headerText="Description"/>
</mx:columns>
</mx:DataGrid>
<mx:Form >
<mx:FormItem label="Name">
<mx:TextInput id="Name2" text="{dg2.selectedItem.name}" />
</mx:FormItem>
<mx:FormItem label="Description">
<mx:TextInput id="Description2" text="{dg2.selectedItem.description}"/>
</mx:FormItem>
</mx:Form>
</mx:Application>


That's All