Thursday, January 1, 2009

Bare Bones BlazeDS Object Remoting

In this blog posting, I will demonstrate using BlazeDS for object remoting between a Flex client and a Java EE-based server. There are several good blog postings and articles on BlazeDS remoting out there already (see Additional Resources section below), but most of them either don't provide a complete example or apply FlexBuilder for some of the "magic." In this post, I will not use FlexBuilder and any IDE or text editor can be used. This allows one to see all the underlying details directly.


Tools Used in these Examples

For the examples in this blog posting, I will be using the following tools:

  • BlazeDS - I am using BlazeDS 3.2 and I am using the binary distribution (not Turnkey that includes Tomcat) to be more "bare bones."

  • Flex 3 SDK - I am using Flex 3.2. For convenience, I have set an environment variable FLEX_HOME to point to the main directory of my Flex 3 installation (where I unzipped it) and I have included %FLEX_HOME%\bin (or $FLEX_HOME/bin) on my PATH.

  • Java SE SDK - I am using Java SE 6 Update 10, but a newer or older version of Java SE should be fine as long as you are using at least J2SE 5.

  • Java EE web implementation - The BlazeDS Turnkey includes Tomcat, but I am using GlassFish for my examples. In theory, it should not matter which Java EE-compliant web server you use.

  • Ant (optional) - I provide a build.xml Ant build file in this posting that will build the Flex clients and will build the BlazeDS/JEE-powered server. However, these operations can be done individually as well. While I build a WAR file with my Ant build for the server-side deployment, one could simply edit directories and files in one's favorite server's deployment directory for the same effect. Similarly, one could build the Flex clients individually on the command line with the mxmlc command rather than using the build.xml file to do this. I am using Ant 1.7, but I am not aware of any features I am using specific to that version. Finally, I have my ANT_HOME environment variable set to my installation of Ant and have %ANT_HOME%/bin in my path for convenience in building.

  • Text Editor or IDE - You could use FlexBuilder, but do not need to for the examples in this posting. Other choices include NetBeans, Eclipse, JDeveloper, IDEA, JEdit, SpketIDE, vim, emacs, and even Notepad/Wordpad. If you do choose to use an IDE that supports XML, you can make things a little easier for yourself by associating MXML with XML as I demonstrate with NetBeans here and with JEdit here. Also, there are plug-ins for Flex for IDEs such as FlexBean for NetBeans. Also, it is my understanding that IDEA has built-in Flex support.



The following screen snapshot demonstrates the versions of these tools used as described above.




Setting Up BlazeDS for the Application

I won't go into any greater detail regarding installation of the Flex SDK, of the Java SDK, of a Java EE-compliant web server, of Ant, or of an IDE or text editor than that provided above. However, I will briefly look a little closer at installation of BlazeDS.

BlazeDS is open source and its source can be downloaded here, but the source code for BlazeDS is not needed for any of the examples in this blog posting.

The BlazeDS download page includes release builds and nightly builds. I am using a release build for the examples here. BlazeDS 3.2 Release Build 3978 is available as of this writing and I am using BlazeDS 3.2 Release Build 3978 for these examples.

An advantage of using the Turnkey download is that Tomcat web server is included with it. However, I intentionally want the examples in this posting to be "bare bones" and so will use a separate JEE implementation. With the non-Turnkey binary BlazeDS release downloaded (it will be called something like blazeds-bin-3.2.0.3978.zip), "installation" simply involves unzipping the ZIP file and extracting the WAR file (blazeds.war) from it. The next screen snapshot shows the contents of this binary BlazeDS download, including the WAR file just mentioned.



With access to the blazeds.war file, we now need to expand the blazeds.war's contents. We have two choices here. One popular choice is to expand this WAR file's contents in a web server's deployment directory. Many web servers will then automatically deploy the contents of that expanded directory. However, I will be taking the second approach here and will rebuild the WAR file for deployment with an Ant script. This provides greater portability between web servers and allows me to show better what needs to be included in the server-side BlazeDS-powered WAR file via the build.xml file. You should be able to use the direct deployment of expanded directory approach and not need Ant for the server-side if you prefer that approach.

The next screen snapshot shows the contents of the blazeds.war file that needs to be expanded.



For my examples, I will be expanding the blazeds.war file's contents into the C:\blazeDSRemotingExample) directory. I typically remove the META-INF directory and its contents from the expanded directory, so that it looks something like that shown in the next two screen snapshots. Note that the red lines through directories in the first screen snapshot are intentional because the entries that they cross out will eventually be built up, but will not be there when blazeds.war is initially expanded (in other words, your newly expanded directory should only have the WEB-INF directory if you deleted the META-INF directory as I did).





As the immediately preceding image shows, the expanded blazeds.war contents include some expected subdirectories beneath WEB-INF including classes and lib. Because we will rebuild our server's WAR file with this same expanded directory structure, we already have the necessary directories we need for the web application's classpath.


Tailoring BlazeDS Web Descriptor Configuration for Custom Application

Up until now, we've merely unzipped the downloaded binary BlazeDS file and then expanded the blazeds.war file into a directory. Now it is time to begin tweaking the configuration files included with the blazeds.war file to accommodate our application.

One of the first things I like to do when customizing my own BlazeDS-based application based on the provided blazeds.war file is to upgrade the web application descriptor file (web.xml) to reflect Servlet 2.5 rather than Servlet 2.3. I provide more detail on this process in my blog entry on OpenLaszlo with Java Servlets 2.5 (OpenLaszlo also provides an older web.xml file). The most important changes to note are that the DTD-described file becomes W3C XML Schema described instead, the version attribute is obviously updated from 2.3 to 2.5, and the <display-name> element is removed as a nested element under the <servlet> element. This particular change of versions of the web.xml file is probably not absolutely necessary in this case because I'm not using Java EE 5 annotations, but I make the change every time anyway.

The less important changes that I make to the provided web.xml file include removal of commented out section specifically intended for WebSphere (which I'm not using here) and other comments. The other change I make is to change the text for the high-level <display-name> and <description> elements. Indeed, when I have completed the transformation, the new web.xml file still looks very similar to the one provided with the blazeds.war file. Several of these changes are depicted in the next screen snapshot taken of a diff run against the two versions of web.xml using the ExamDiff tool.



The new and customized web.xml file is shown next.

New and Customized web.xml File

<?xml version="1.0" encoding="UTF-8"?>
<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">
<display-name>Dustin's BlazeDS Hello World Example</display-name>
<description>An extremely simple BlazeDS Remoting Application</description>

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

<!-- MessageBroker Servlet -->
<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>

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

</web-app>



Customizing Other BlazeDS Configuration Files

Besides the classes and lib subdirectories contained under WEB-INF in the directory expanded from the blazeds.war file, we also have subdirectories called src and flex. We will not customize a couple XML files inside the flex directory to tailor BlazeDS for our application's use.

The first file we'll look at is called services-config.xml. This file is central to all BlazeDS-based applications whether they are based on RPC or messaging. In fact, among other things, this file usually imports additional configuration files for things that are specific to proxying, to remoting, and to messaging.

I use the same approach with services-config.xml that I used with web.xml: I adapt the version provided by blazeds.war to work for the custom application. However, there is a twist in this one case. I want to have a slightly different version of this file for my Flex client than I have for my WAR that gets deployed on the server. Keeping the DRY principle in mind, my approach here is to create a services-config-template.xml file and use Ant's text substitution functionality to automatically generate the services-config.xml file to be used by both the Flex client and by the WAR that is deployed to the server. The next code listing shows the template file, which I placed in a newly created directory creatively called "templates".

templates/services-config-template.xml

<?xml version="1.0" encoding="UTF-8"?>
<services-config>

<services>
<service-include file-path="remoting-config.xml" />
<service-include file-path="proxy-config.xml" />
<service-include file-path="messaging-config.xml" />
</services>

<security>
<login-command class="flex.messaging.security.TomcatLoginCommand" server="Tomcat"/>
</security>

<channels>
<channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://@host@:@port@/{context.root}/messagebroker/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
<channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
<endpoint url="https://@host@:@port@/{context.root}/messagebroker/amfsecure"
class="flex.messaging.endpoints.SecureAMFEndpoint"/>
<properties>
<add-no-cache-headers>false</add-no-cache-headers>
</properties>
</channel-definition>

<channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://@host@:@port@/{context.root}/messagebroker/amfpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>true</polling-enabled>
<polling-interval-seconds>4</polling-interval-seconds>
</properties>
</channel-definition>
</channels>

<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Error">
<properties>
<prefix>[BlazeDS] </prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
<filters>
<pattern>Endpoint.*</pattern>
<pattern>Service.*</pattern>
<pattern>Configuration</pattern>
</filters>
</target>
</logging>

<system>
<redeploy>
<enabled>false</enabled>
</redeploy>
</system>

</services-config>


The code for the template version of services-config-template.xml shown immediately above includes Ant token markers (@host@ and @port@) that will be replaced by Ant with an actual host name and a port number for the client's version of services-config.xml). These same tokens will be replaced with BlazeDS-recognized tokens {server.name} and {server.port} for the server-side version of services-config.xml. Note that I was able to leave the BlazeDS template {context.root} in the template for the client-side version of services-config.xml because the mxmlc compiler allows me to specify its value for the client as an option.

I left the reference in services-config.xml to all three previously referenced files (remoting-config.xml, proxy-config.xml, and messaging-config.xml), but really only need the reference to remoting-config.xml for this blog post examples. As with the web.xml file, my approach to creation of that referenced remoting-config.xml file is to simply adapt the one provided with the expanded blazeds.war file. The only necessary change is to specify the Java classes that will be used on the server side. We haven't looked at this Java class yet, but the remoting-config.xml file shown in the next code listing tips us off that it will be a class named HelloWorldRemotingServer in a package named dustin.

remoting-config.xml

<?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="HelloWorld">
<properties>
<source>dustin.HelloWorldRemotingServer</source>
</properties>
</destination>
</service>



The above remoting-config.xml file specifies that the destination associated with this Java class on the server side can be referenced as "HelloWorld" (the 'id' attribute of the destination element).

Whew! That wraps up the BlazeDS configuration modifications. If you have not looked into this before, it looks worse than it really is. Most of the final content shown in the code listings above was already provided by the blazeds.war that we expanded. I only made some relatively small modifications to the web.xml and remoting-config.xml files. The services-config.xml file likewise only required some minor changes, but the more significant issue there was that I needed to prepare a different version of this file for the client than for the server. The server version of services-config.xml can resolve the BlazeDS tokens {host.name} and {host.port} based on the web server settings, but I specify a hard-coded host name and port for the client version of services-config.xml.


The Java Class

The contents of the remoting-config.xml file provided us with a sneak peek at some details about the Java class on the server. The definition of this simple Java class is shown next. Note that this file is placed in a subdirectory called dustin (because it is in the "dustin" Java package) under the src subdirectory under WEB-INF in our expanded directory.

HelloWorldRemotingServer.java (1st Version)

package dustin;

/**
* Simple "Hello World" example meant for use in BlazeDS remoting example.
* This is the server-side POJO that will be exposed to a Flex client via
* BlazeDS remoting.
*
* @author Dustin
*/
public class HelloWorldRemotingServer
{
/** No-arguments constructor. */
public HelloWorldRemotingServer() {}

/**
* Accept a String representing a name and return that name as part of a
* Hello message.
*
* @param nameToSayHelloTo Name to which Hello will be addressed.
* @return Hello address to provided name.
*/
public String processHello(final String nameToSayHelloTo)
{
return "Hello, " + nameToSayHelloTo + "!";
}
}


The important thing to note from the code listing for the Java class immediately above is that it is a regular Java class with no knowledge of Flex or ActionScript.


The Flex Client (Hello World Client)

The Java class we just looked at returns a provided String surrounded with a Hello World prefix and exclamation point. We'll now look at the Flex MXML code that can be used to invoke this Java object and this processHello(String) method via BlazeDS.

HelloWorldRemotingClient.mxml

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="1100" height="700">

<!-- Associate client with BlazeDS destination via RemoteObject. -->
<mx:RemoteObject id="remoteObject" destination="HelloWorld" />

<mx:Panel id="mainPanel" title="Simple BlazeDS Remoting Example - Hello World">
<mx:Form>
<mx:FormItem label="Provide Your Name">
<mx:TextInput id="textInput"
change="remoteObject.processHello(textInput.text)"/>
</mx:FormItem>
<mx:FormItem label="Server's Response">
<mx:Label text="{remoteObject.processHello.lastResult}"/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
</mx:Application>


The MXML code above is very simple. The RemoteObject element (with an 'id' of "remoteObject") specifies a destination (via the 'destination' attribute) of "HelloWorld" (defined above in the remoting-config.xml file).

If you plan to use the build.xml file provided in this blog posting, this MXML file should be placed at parallel to the WEB-INF directory (as opposed to in the WEB-INF directory like the Java class and configuration files previously discussed).


Building it All

Even though I still need to show another Flex client, an ActionScript class, and another Java class whose instantiation is bound to the ActionScript class's instantiation, I am including the entire Ant build.xml file here for convenience. Some of the targets defined in this file will not work until the remaining classes discussed later in this blog posting are included.

build.xml (Ant)

<?xml version="1.0" encoding="UTF-8"?>
<project name="HelloWorldBlazeDSRemotingExample" default="all" basedir=".">
<description>Builds, tests, and runs the Dustin's Hello World BlazeDS Remoting Example.</description>

<!-- NOTE TO USERS: At least three properties set below may need to be
set for this build file to work with different deployment
environments. The three minimum properties to examine
are client.host, client.port, and app.dir.
-->

<!-- GENERAL PROPERTIES -->
<!--
*********************************************************************
Change server's host name and port (determined by web server
deployment of server side of code).
*********************************************************************
-->
<property name="client.host" value="localhost" />
<property name="client.port" value="8080" />
<!--
*********************************************************************
Change definition of app.dir if necessary (to where blazeds.war
has been uncompressed).
*********************************************************************
-->
<property name="app.dir" value="C:\\blazeDSRemotingExample" />
<property name="web.inf.dir" value="${app.dir}/WEB-INF" />
<property name="templates.dir" value="${app.dir}/templates" />
<property name="context.name" value="HelloWorldRemotingServer" />


<!-- GENERAL JAVA PORTION PROPERTIES -->
<property name="src.dir" value="${web.inf.dir}/src" />
<property name="classes.dir" value="${web.inf.dir}/classes" />
<property name="lib.dir" value="lib" />
<property name="dist.dir" value="${app.dir}/dist" />
<property name="javadoc.dir" value="${dist.dir}/javadoc" />
<property name="serverside.war" value="${context.name}.war" />

<!-- Nothing special needed for classpath! -->
<path id="classpath" />


<!-- BLAZEDS RELATED PROPERTIES -->
<property name="services.config.server.name"
value="services-config.xml" />
<property name="services.config.client.name"
value="services-config-client.xml" />
<property name="services.config.template"
value="services-config-template.xml" />


<!-- GENERAL FLEX PORTION PROPERTIES (FLEX CLIENT) -->
<property name="app.flex.dir" value="${web.inf.dir}/flex" />
<property name="flex.debug" value="true" />
<property name="flex.warnings" value="true" />
<property name="flex.context.root" value="${context.name}" />
<property name="flex.services"
value="${app.flex.dir}/${services.config.client.name}" />
<property name="flex.source.path" value="${web.inf.dir}" />
<property name="flex.publisher" value="Dustin" />
<property name="flex.title"
value="'Hello World BlazeDS Remoting Example'" />
<property name="flex.description"
value="'Simple client for BlazeDS Remoting example'" />
<property name="flex.verbose.stacktraces" value="true" />
<property name="flex.actionscript.warnings" value="true" />
<property name="flex.app.helloworld.name.root"
value="HelloWorldRemotingClient" />
<property name="flex.app.helloworld.name.source"
value="${flex.app.helloworld.name.root}.mxml" />
<property name="flex.app.helloworld.name.swf"
value="${flex.app.helloworld.name.root}.swf" />
<property name="flex.app.complexobject.name.root"
value="ComplexObjectRemotingClient" />
<property name="flex.app.complexobject.name.source"
value="${flex.app.complexobject.name.root}.mxml" />
<property name="flex.app.complexobject.name.swf"
value="${flex.app.complexobject.name.root}.swf" />



<target name="-init">
<mkdir dir="${dist.dir}" />
<mkdir dir="${javadoc.dir}" />
</target>

<target name="-copy-respective-services-config-files"
depends="-clean-generated-blazeds-config-files">
<copy file="${templates.dir}/${services.config.template}"
tofile="${app.flex.dir}/${services.config.server.name}" >
<filterchain>
<replacetokens>
<token key="host" value="{server.name}"/>
<token key="port" value="{server.port}"/>
</replacetokens>
</filterchain>
</copy>
<copy file="${templates.dir}/${services.config.template}"
tofile="${app.flex.dir}/${services.config.client.name}" >
<filterchain>
<replacetokens>
<token key="host" value="${client.host}"/>
<token key="port" value="${client.port}"/>
</replacetokens>
</filterchain>
</copy>
</target>

<target name="compile"
depends="compileJavaBasedServer, compileFlexBasedClients"
description="Compile Java and Flex code." />

<target name="compileJavaBasedServer"
description="Compile Java source code used for server side.">
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
classpathref="classpath" />
</target>

<target name="compileFlexBasedClients"
description="Compile the Flex clients"
depends="compileFlexBasedHelloWorldClient,
compileFlexBasedComplexObjectClient" />

<target name="compileFlexBasedHelloWorldClient"
description="Compile Flex Hello World client application into SWF file.">
<exec executable="mxmlc">
<arg line="-debug=${flex.debug}" />
<arg line="-context-root=${flex.context.root}" />
<arg line="-services=${flex.services}" />
<arg line="-warnings=${flex.warnings}" />
<arg line="-publisher=${flex.publisher}" />
<arg line="-title=${flex.title}" />
<arg line="-description=${flex.description}" />
<arg line="-verbose-stacktraces=${flex.verbose.stacktraces}" />
<arg line="-show-actionscript-warnings=${flex.actionscript.warnings}" />
<arg line="-output ${dist.dir}/${flex.app.helloworld.name.swf}" />
<arg line="-- ${flex.app.helloworld.name.source}" />
</exec>
</target>

<target name="compileFlexBasedComplexObjectClient"
description="Compile Flex Complex Object client application into SWF file.">
<exec executable="mxmlc">
<arg line="-debug=${flex.debug}" />
<arg line="-context-root=${flex.context.root}" />
<arg line="-services=${flex.services}" />
<arg line="-warnings=${flex.warnings}" />
<arg line="-publisher=${flex.publisher}" />
<arg line="-title=${flex.title}" />
<arg line="-description=${flex.description}" />
<arg line="-verbose-stacktraces=${flex.verbose.stacktraces}" />
<arg line="-show-actionscript-warnings=${flex.actionscript.warnings}" />
<arg line="-output ${dist.dir}/${flex.app.complexobject.name.swf}" />
<arg line="-- ${flex.app.complexobject.name.source}" />
</exec>
</target>

<target name="war"
description="Build server's web application archive (WAR)."
depends="-init,
-copy-respective-services-config-files,
compileJavaBasedServer">
<war webxml="${web.inf.dir}/web.xml"
destfile="${dist.dir}/${serverside.war}"
filesonly="true">
<zipfileset dir="${web.inf.dir}"
excludes="web.xml
flex/${services.config.client.name}"
prefix="WEB-INF" />
</war>
</target>

<target name="all" depends="war, compileFlexBasedClients"
description="Build it all; the default target." />

<target name="clean" description="Remove generated files."
depends="-clean-generated-blazeds-config-files">
<delete dir="${dist.dir}" />
<delete dir="${classes.dir}/dustin" />
</target>

<target name="-clean-generated-blazeds-config-files">
<delete file="${app.flex.dir}/${services.config.server.name}"
quiet="true" />
<delete file="${app.flex.dir}/${services.config.client.name}"
quiet="true"/>
</target>

<target name="javadoc" description="Generate Javadoc-based documentation">
<javadoc doctitle="Dustin's Spring BlazeDS Integration Example"
destdir="${javadoc.dir}"
sourcepath="${src.dir}"
classpathref="classpath"
private="true"
author="Dustin" />
</target>

</project>


In the above build.xml file, there are three properties defined near the top that would most likely need to be changed for a different deployment environment. They are client.host, client.port, and app.dir. The first two are used to appropriately build the client's version of services-config.xml to reference the host and port on which the server BlazeDS-powered application will be running. Because I am using GlassFish defaults for my server, I define these properties respectively to localhost and 8080. The third property, app.dir defines the directory into which I expanded the contents of the blazeds.war file for editing.

Targets of specific interest in the above build.xml file include the compileFlexBasedHelloWorldClient target for building the Flex client with the mxmlc compiler and the war target for building the WAR file to be deployed on the server.

When I build the WAR and the client SWF file (same name as its source MXML file but with an .swf extension rather than the .mxml extension), I can then deploy the WAR and run the SWF client. The deployment of the WAR file is done with whatever mechanism your web serve provides. The SWF client can easily be executed by clicking on it or typing its full name on the command line. The following two screen snapshots show the client running by first showing how it initially appears and then showing how it appears once a name has been entered into the name field.






Binding Java to ActionScript

The above example demonstrates the ability to remotely call Java on the server from MXML on the client via the RemoteObject and BlazeDS. However, the example up to this point has only used simple mappings (in this case Strings). It is more interesting to now move onto an example of binding a more complex objects. The good news is that it is all still pretty easy.

For this slightly more complex example, we will expand the HelloWorldRemotingServer class to feature a method that accepts a domain object that is neither a primitive, a boxed version of a primitive, or a String. The revised version of HelloWorldRemotingServer is shown next:

HelloWorldRemotingServer.java (Finished Version)

package dustin;

/**
* Simple "Hello World" example meant for use in BlazeDS remoting example.
* This is the server-side POJO that will be exposed to a Flex client via
* BlazeDS remoting.
*
* @author Dustin
*/
public class HelloWorldRemotingServer
{
/** No-arguments constructor. */
public HelloWorldRemotingServer() {}

/**
* Accept a String representing a name and return that name as part of a
* Hello message.
*
* @param nameToSayHelloTo Name to which Hello will be addressed.
* @return Hello address to provided name.
*/
public String processHello(final String nameToSayHelloTo)
{
return "Hello, " + nameToSayHelloTo + "!";
}

/**
* Accept a last name and a first name and return the concatendated name as
* part of a James Bond like introduction.
*
* @param firstName Provided first name of person.
* @param lastName Provided last name of person.
* @return The James Bond like introductory message.
*/
public String processName(final String firstName, final String lastName)
{
return "Your name is " + lastName + ". " + firstName + " " + lastName + ".";
}

/**
* Accept a Person and return that person's name as part of a James Bond like
* introduction.
*
* @param person Person whose name will be returned.
* @return The James Bond like introductory message.
*/
public String processName(final Person person)
{
return "Your name is " + person.getLastName() + ". "
+ person.getFirstName() + " " + person.getLastName() + "!";
}
}


One of the newly added methods is called processName(String,String). This wouldn't make a very exciting example because it still uses two separate Strings. The more interesting example will be binding a Flex client to the processName(Person) method that expects a Person instance. For our newly updated class to compile, we need to define a Person class in the same package. That is shown next:

Person.java

package dustin;

/**
* Encapsulates a person's information.
*
* @author Dustin
*/
public class Person
{
/** Last name of person. */
private String lastName;

/** First name of person. */
private String firstName;

/** No-arguments constructor. */
public Person() {}

/**
* Constructor accepting names for this person.
*
* @param newLastName Last name of the person.
* @param newFirstName First name of the person.
*/
public Person(final String newLastName,
final String newFirstName,
final int newAge)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}

/**
* Provide person's first name.
*
* @return Person's first name.
*/
public String getFirstName()
{
return this.firstName;
}

/**
* Set/change the person's first name.
*
* @param newFirstName New first name for the person.
*/
public void setFirstName(final String newFirstName)
{
this.firstName = newFirstName;
}

/**
* Provide person's last name.
*
* @return Person's last name.
*/
public String getLastName()
{
return this.lastName;
}

/**
* Set/change person's last name.
*
* @param newLastName New last name for person.
*/
public void setLastName(final String newLastName)
{
this.lastName = newLastName;
}

/**
* String representation of me.
*
* @return My String representation.
*/
@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}


There is nothing remarkable about the Person class and there is no sign of any association with ActionScript or Flex in this Java class. In fact, the Person class is downright boring.

For Strings, primitives, and object/boxed versions of primitives, BlazeDS can typically map these types to appropriate ActionScript types. Now that we've introduced the Person type, however, we need to instruct BlazeDS how to use this Java class on the Flex-based client side. This is easily done by simply creating a parallel Person class in ActionScript that explicitly associates itself with the Java version of Person. The code for the ActionScript version of Person is shown next.

Person.as

package dustin
{
[Bindable]
[RemoteClass(alias="dustin.Person")]
public class Person
{
/** First name of person. */
private var _firstName:String;

/** Last name of person. */
private var _lastName:String;

/**
* Constructor accepting names of this newly instantiated person.
* Because each parameter has a default of null, this can be used as a
* no-arguments constructor.
*
* @param newFirstName First name to be used for this person.
* @param newLastName Last name to be used for this person.
*/
public function Person(
newFirstName:String = null,
newLastName:String = null)
{
_firstName = newFirstName;
_lastName = newLastName;
}

/**
* WRITE (set/change) firstName property.
*
* @param newFirstName New first name.
*/
public function set firstName(newFirstName:String):void
{
_firstName = newFirstName;
}

/**
* READ firstName property.
*
* @return My first name.
*/
public function get firstName():String
{
return _firstName;
}

/**
* WRITE (set/change) lastName property.
*
* @param newLastName new last name.
*/
public function set lastName(newLastName:String):void
{
_lastName = newLastName;
}

/**
* READ lastName property.
*
* @return My last name.
*/
public function get lastName():String
{
return _lastName;
}
}
}


Arguably the most interesting point about the ActionScript version of Person is the use of RemoteClass metadata tag ([RemoteClass(alias="dustin.Person")]) to explicitly associate this ActionScript class with the Java class. By naming the property write/set and read/get methods to match those in the Java class, the remaining binding of ActionScript object to Java object is handled by BlazeDS.

The ActionScript Person.as file should be placed in a newly created subdirectory named dustin (for the "dustin" package) parallel to the WEB-INF directory. We now need to move onto building a new Flex client to use this new method on the server-side.

I want to make one final observation regarding the Person class in ActionScript. The code would have been even more concise if I had made the data members public. In that case, they could be accessed directly and the set and get methods would not be necessary. Many of the online examples take this approach.


A New Flex Client (Complex Object Client)

The code for ComplexObjectRemotingClient.mxml is shown next. It should be placed in the same location as our previous Flex client (in expanded directory and parallel to the WEB-INF subdirectory).

ComplexObjectRemotingClient.mxml

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="1100" height="700">

<mx:Script>
import dustin.Person;

/**
* Prepare provided name information and send to server.
*
* @param firstName First name of person.
* @param lastName Last name of person.
*/
public function processAndSubmitName(firstName:String, lastName:String):void
{
const person:Person = new Person(firstName, lastName);
remoteObject.processName(person);
}
</mx:Script>

<!-- Associate client with BlazeDS destination via RemoteObject. -->
<mx:RemoteObject id="remoteObject" destination="HelloWorld" />

<mx:Panel id="mainPanel" title="BlazeDS Remoting Example - Complex Object Mapping">
<mx:Form>
<mx:FormItem label="Your First Name">
<mx:TextInput id="firstNameInput" />
</mx:FormItem>
<mx:FormItem label="Your Last Name">
<mx:TextInput id="lastNameInput" />
</mx:FormItem>
<mx:FormItem>
<mx:Button label="Submit Name!"
click="processAndSubmitName(firstNameInput.text, lastNameInput.text);" />
</mx:FormItem>
<mx:FormItem label="Server's Response">
<mx:Label text="{remoteObject.processName.lastResult}" />
</mx:FormItem>
</mx:Form>
</mx:Panel>
</mx:Application>


The new Flex client uses a little ActionScript to prepare the call to the server and instantiates one of our newly written Person objects in the process. The build.xml file shown previously already included the target for building this new client ("compileFlexBasedComplexObjectClient"). Note that the Java code also needs to be rebuilt and the WAR reassembled and redeployed to the web server because of the addition of methods to the server class.

The following two screen snapshots show the initial and final state of the new client when it is executed as the Hello World client was earlier.





As the above output shows, the method accepting a Person object (note the exclamation point in the returned message) has been contacted on the Java side from a remote Flex client.


Pictorial Overview of Directory/File Structure

One of the things that can be a little tricky about using BlazeDS or the mxmlc compiler if you have very little experience using them is the placement of files. Fortunately, most of this was already setup correctly by the blazeds.war file that we expanded. However, I did add some directories and files. In an effort to make it more clear where these directories and files are in relation to one another, I include the next screen snapshot of my final directory structure. I tried in the text above to describe where within these various directories each file should go.



The image above shows where to place MXML source files, ActionScript source files, and Java source files for the build.xml file described above to work properly. I did not provide descriptions of the nbproject subdirectory (used for NetBeans specifically), the images subdirectory (used to store the screen snapshots shown in this blog posting), or the WEB-INF subdirectory (it was provided by the expanding of blazeds.war). The only thing to note here is that I added the Java source code files (but not ActionScript source code files) into the src directory under WEB-INF.


Conclusion

This has turned out to be a rather lengthy blog entry. However, this is to be expected considering that I've intentionally tried to make this a complete and thorough introduction to BlazeDS/Java remoting and because I have included the code directly within the blog posting rather than as a separate attachment. Also, I've tried to avoid use of anything specific to FlexBuilder so that anyone with any text editor should be able to use my examples.

Despite the length of this blog entry, use of the BlazeDS remoting is pretty straightforward once you have tried it a couple times. It does get more complex when security constraints and other details are added back in, but one can choose how much one implements at each step and the examples above are nice for confirming that client-to-server communication is working properly.


Additional Resources

3 comments:

Unknown said...

Excellent article about how to get started with Flex & Blazeds. I was looking for information about how to setup the folder structure to work with both Flex and Blazeds and this article answered my questions. I'm going to use your sample as a template for my project and extend it by hiding the remote objects from the Flex GUI and placing then in my business components, and use the Mate framework to glue all together. In the server side I'm going to use Spring + Blazeds + Ibatis + MySQL.

Thanks,

Alberto

Unknown said...

Excellent article about how to get started with Flex & Blazeds. This article answered my question about how to setup the folder structure of my project. I'm going to use your sample as a template for my project and I'm going to extend it by hiding the remote objects from the Flex GUI by placing them inside my business components, and use the Mate framework to glue all together on the Flex side. In the server side I'm going to extend your sample by integrating Spring + iBatis.

Thanks,

Alberto

harshsondhi said...

Good article, if someone using jboss make sure to take care of crossdomain stuff appropriately.