Java EE and Flex, Part 1: A compelling combination

Rich-client technology for your enterprise Java applications

Adobe Flex is becoming a popular choice for generating the client side of enterprise Java applications. In this first of two articles, Dustin Marx demonstrates how Flex can help you deliver highly interactive user interfaces that access your Java EE application's enterprise logic. Get a hands-on introduction to perfecting a simple Flex client, then enable it to communicate with your Java EE server. Level: Beginner

Flex 3 gives you another choice for building browser-based UIs for your Java EE applications. If you haven't yet discovered how simple it is to add rich clients to enterprise Java applications with Flex, this article could serve as your point of entry. You'll find out what benefits Flex brings to the table, how to create application layouts using Flex's XML grammar, and how to make your Flex client work with a Java EE application.

Java developers adopting Flex

We know that some Java developers are resistant to Flex as a front-end technology for Java EE, but there's a strong argument for giving Flex a chance. Author Dustin Marx discusses the factors driving Flex adoption in the Java community in a sidebar to this hands-on article.

Before I ask you to install Flex and start putting together a sample application, let's consider the advantages of using Flex as a client-side technology. Flex offers advantages specific to Java developers and some that are more general. We'll look at both.

Why choose Flex?

Adopting a new technology means embracing a learning curve, which can take some convincing. Here are some general advantages to using Flex:

  • You can write Flex code once and run it in any Web browser for which a Flash Player plugin exists. None of the browser-detection or object-detection code typical of JavaScript or Ajax applications is required.
  • The target runtime (Flash Player 9 or later) is installed on more than 95 percent of Web browsers worldwide.
  • Flex is based on standards. Its scripting language (ActionScript 3.0) has roots in ECMAScript (the same specification implemented by JavaScript), and its layout language is a specific XML grammar called MXML. Familiarity with the underlying standards can help you learn Flex with relative ease.
  • Flex has a refreshingly simple mechanism for binding the property of one object in a Flex application to the property of another object in Flex. This addictive feature is commonly referred to as property binding. (JSR 295: Beans Binding is intended to add this feature to the Java language, but it won't be included in Java SE 7.)
  • You can associate the Flex-based front-end with any back-end technology using techniques that promote loose coupling. Flex provides built-in support for communication with back-ends via both traditional HTTP and SOAP-based Web services.
  • Flex provides a rich set of components, Flash effects (including animation, video, and audio), and accessibility features that make it easy to add richness and highly fluid experiences to a Web application.

Flex for Java developers

General benefits might be enough to attract you to Flex, but there are others that are mostly or entirely aimed at Java developers.

One such benefit is the striking similarity between Java and ActionScript 3.0 in language features, concepts, and syntax. The languages use similar conditional statements, looping syntax, and even coding conventions. (It's arguable that ActionScript is more like Java than JavaFX Script.) Flex's Javadoc-like ASDoc documentation-generation tool uses the same comment syntax that you use in Java to generate documentation. ActionScript's packaging structure is related to directory structure in exactly the same way that Java approaches packages and directories.

ActionScript 3 also provides class-based object-oriented features (such as classes in the Java sense, inheritance, and interfaces) and static typing. These additions to what most of us are used to in JavaScript make learning and using ActionScript easier. (ActionScript still makes dynamic typing and prototype-based inheritance available for situations when you want or need those features of traditional JavaScript.)

Flex's ability to communicate with a Java EE back-end using HTTP or SOAP-based Web services is highly useful, but you're not limited to those communication approaches. Blaze DS -- a separate, open source product from Adobe -- gives you even greater flexibility for communicating between a Flex front-end and a Java EE back-end. BlazeDS lets you use JMS for communication and allows you to use object remoting with Java. BlazeDS also adds potential performance benefits because it uses the binary AMF3 format for faster communication than is normally experienced with XML.

A third-party open source product called GraniteDS offers even more choices for applying a Flex-based front-end to a Java EE application. GraniteDS offers support for the AMF3 binary format and also some features not available with BlazeDS. For example, GraniteDS offers tools and service frameworks for more easily integrating Flex with back-ends based on EJB 3, the Spring Framework, Guice, or Seam.

In discussing Flex so far, I've repeatedly used the words simple and easy. But don't just take my word for it. The best way to understand how simple and easy Flex basics are is to try them out for yourself. In the next sections you'll implement a sample application, refactor it to add features and reduce boilerplate code, and then establish communication between your new, Flex-based client and a Java servlet.

Acquiring and installing Flex

This article's examples use the Flex 3.2 SDK. If you want to build and run the examples, download the Flex SDK (including the command-line compiler and debugger). A single ZIP file contains the Flex SDK for multiple platforms.

Unzip the file into an obvious location, such as C:\flex_sdk_3_2. For convenience, add the location of the Flex SDK bin directory in the path so that the command-line tools can be run from any directory. I like to create a FLEX_HOME environment variable that points at the Flex SDK location and then add $FLEX_HOME/bin or %FLEX_HOME%\bin to the PATH. You can verify a correct installation of Flex by running the command mxmlc -version, as shown in Figure 1.

Verifying your Flex installation
Figure 1. Verifying your Flex installation

Although it's not required to build and run the examples, you might be interested in downloading FlexBuilder 3, which is available at no cost for a trial period. FlexBuilder lets you use any text editor (such as JEdit or vim) or Java IDE (such as NetBeans or Eclipse) to write and maintain MXML and ActionScript files. Aptana Studio and Spket IDE include specific support for editing Flex-related files.

MXML: Flex layout with XML

Flex uses MXML for defining a Flex application's layout. Flex layout files are typically named with a .mxml extension. MXML code must be well-formed XML and use XML namespaces. The example in Listing 1 demonstrates a simple but completely functional Flex application, written entirely with MXML, that displays a list of selected JavaWorld articles.

Listing 1. Static MXML example

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                width="1100" height="700">
   <mx:Panel title="Some of Dustin's Favorite JavaWorld Articles">
      <mx:Label text="A Small Sample of Favorite JavaWorld Articles"
                fontSize="14" fontWeight="bold" />
      <mx:Grid>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Designing with Exceptions"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Bill Venners" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-07-1998/jw-07-techniques.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Inheritance Versus Composition"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Bill Venners" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="JSP Best Practices"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Dustin Marx" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="More JSP Best Practices"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Dustin Marx" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Why Extends is Evil"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Allen Holub" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Why Getter and Setter Methods Are Evil"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Allen Holub" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="New Features Added to Servlet 2.5"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Jason Hunter" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="REST for Java Developers, Part 1"
                         fontWeight="bold" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Brian Sletten" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html" />
            </mx:GridItem>
         </mx:GridRow>
      </mx:Grid>
   </mx:Panel>
</mx:Application>

Because this example is static, it doesn't show off many of Flex's and Flash's advantages. However, it serves as a good introduction to MXML.

All of the code in Listing 1 is well-formed XML. Most of the XML lines in Listing 1 are related to the same lines of code (repeating GridRow elements with nested GridItem and Label elements). They are used to define a static display grid with the Grid component and its GridRow and GridItem sub-elements. The use of <Grid>, <GridRow>, and <GridItem> organize and present data in a manner similar to how HTML table elements <table>, <tr>, and <td>, respectively, are often used.

This first MXML example also demonstrates the <mx:application> root tag used in all MXML applications. This tag includes an explicit width and height for the Flex application. The mx prefix is associated with the Flex XML namespace as part of this root element.

You'll use the Flex command-line compiler, mxmlc, to compile this article's examples. The Flex defaults (defined in the flex-config.xml file) are sufficient for the examples' needs, making compilation with mxmlc easy. Assuming the first MXML listing is saved in a file named Example1.mxml, you compile it with this command:

mxmlc Example1.mxml

In accordance with the default settings, this MXML file is compiled into an SWF file, called Example1.swf, that's placed in the same directory as the MXML file it was generated from. You can execute the SWF file by opening it in a Web browser or by simply entering the entire filename at the command line. The rendered SWF file looks something like Figure 2.

Rendered SWF file
Figure 2. Rendered SWF file (Click to enlarge.)

This is a completely static example. Even the displayed URLs are not clickable. Also, the bolding of text is applied on a per-element basis rather than by using the Cascading Style Sheets (CSS) approach.

Adding styles and clickable URLs

Listing 2 is adapted from Listing 1. It specifies styles using CSS-compliant syntax and then applies them to the individual Flex elements using the respective styleName attributes. This improved example also makes the displayed URLs act like normal clickable HTML links.

Listing 2. Adapted MXML example using CSS and hyperlinks

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                width="1100" height="700">
   <mx:Style>
      .articleTitle
      {
         font-weight: bold;
      }
      .gridLabel
      {
         font-weight: bold;
         font-size: 14;
      }
   </mx:Style>
   <mx:Panel title="Some of Dustin's Favorite JavaWorld Articles">
      <mx:Label text="A Small Sample of Favorite JavaWorld Articles"
                styleName="gridLabel" />
      <mx:Grid>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Designing with Exceptions"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Bill Venners" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="exceptionsUrl"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-07-1998/jw-07-techniques.html'>http://www.javaworld.com/javaworld/jw-07-1998/jw-07-techniques.html</a>"
                         selectable="true"
                         mouseOut="exceptionsUrl.setStyle('textDecoration', 'none');"
                         mouseOver="exceptionsUrl.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Inheritance Versus Composition"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Bill Venners" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="inheritanceUrl"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html'>http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html</a>"
                         selectable="true"
                         mouseOut="inheritanceUrl.setStyle('textDecoration', 'none');"
                         mouseOver="inheritanceUrl.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="JSP Best Practices"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Dustin Marx" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="jspBestPractices1Url"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html'>http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html</a>"
                         selectable="true"
                         mouseOut="jspBestPractices1Url.setStyle('textDecoration', 'none');"
                         mouseOver="jspBestPractices1Url.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="More JSP Best Practices"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Dustin Marx" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="jspBestPractices2Url"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html'>http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html</a>"
                         selectable="true"
                         mouseOut="jspBestPractices2Url.setStyle('textDecoration', 'none');"
                         mouseOver="jspBestPractices2Url.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Why Extends is Evil"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Allen Holub" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="evilExtendsUrl"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html'>http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html</a>"
                         selectable="true"
                         mouseOut="evilExtendsUrl.setStyle('textDecoration', 'none');"
                         mouseOver="evilExtendsUrl.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Why Getter and Setter Methods Are Evil"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Allen Holub" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="getSetEvilUrl"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html'>http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html</a>"
                         selectable="true"
                         mouseOut="getSetEvilUrl.setStyle('textDecoration', 'none');"
                         mouseOver="getSetEvilUrl.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="New Features Added to Servlet 2.5"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Jason Hunter" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="servletUrl"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html'>http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html</a>"
                         selectable="true"
                         mouseOut="servletUrl.setStyle('textDecoration', 'none');"
                         mouseOver="servletUrl.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="REST for Java Developers, Part 1"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Brian Sletten" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label id="restUrl"
                         htmlText="<a href='http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html'>http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html</a>"
                         selectable="true"
                         mouseOut="restUrl.setStyle('textDecoration', 'none');"
                         mouseOver="restUrl.setStyle('textDecoration', 'underline');" />
            </mx:GridItem>
         </mx:GridRow>
      </mx:Grid>
   </mx:Panel>
</mx:Application>

Save the MXML in Listing 2 to a file called Example2.mxml. Compile it with mxmlc Example2.mxml to generate Example2.swf. Its rendering is shown in Figure 3.

Rendered SWF with hypertext links
Figure 3. Rendered SWF with hypertext links (Click to enlarge.)

Each URL is intentionally styled with an underline that appears only when the mouse hovers over it. This is implemented with the mouseOver and mouseOut attributes that describe event handlers for each URL item.

This second example works fine, but it uses a lot of boilerplate code. This provides a convenient segue to looking at how easy it is to implement a custom component in Flex.

Implementing a custom component

Listing 3 demonstrates the creation of a specialized type of Label that reduces the code in our main application.

Listing 3. UrlLabel.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Label xmlns:mx="http://www.adobe.com/2006/mxml"
          selectable="true"
          mouseOut="this.setStyle('textDecoration', 'none');"
          mouseOver="this.setStyle('textDecoration', 'underline');"
          creationComplete="initializeHtmlText();">
   <mx:Script>
   <![CDATA[
   public function initializeHtmlText():void
   {
      this.htmlText = "<a href='" + this.text + "'>" + this.text + "</a>";
   }
   ]]>
   </mx:Script>
</mx:Label>

Listing 3 provides a new, custom component called UrlLabel. (The name of the component corresponds to the name of the file.) This component implements the common functionality used in the example application for presenting a URL to a JavaWorld article.

Even with common functionality of a URL label refactored out to the UrlLabel component, the Flex application still contains a significant amount of boilerplate code, as shown in Listing 4.

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                xmlns:jw="components.*"
                width="1100" height="700">
   <mx:Style>
   .articleTitle
   {
      font-weight: bold;
   }
   .gridLabel
   {
      font-weight: bold;
      font-size: 14;
   }
   </mx:Style>
   <mx:Panel title="Some of Dustin's Favorite JavaWorld Articles">
      <mx:Label text="A Small Sample of Favorite JavaWorld Articles"
                styleName="gridLabel" />
      <mx:Grid>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Designing with Exceptions"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Bill Venners" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="exceptionsUrl"
                            text="http://www.javaworld.com/javaworld/jw-07-1998/jw-07-techniques.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Inheritance Versus Composition"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Bill Venners" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="inheritanceUrl"
                            text="http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="JSP Best Practices"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Dustin Marx" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="jspBestPractices1Url"
                            text="http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="More JSP Best Practices"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Dustin Marx" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="jspBestPractices2Url"
                            text="http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Why Extends is Evil"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Allen Holub" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="evilExtendsUrl"
                            text="http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="Why Getter and Setter Methods Are Evil"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Allen Holub" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="getSetEvilUrl"
                            text="http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="New Features Added to Servlet 2.5"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Jason Hunter" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="servletUrl"
                            text="http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html" />
            </mx:GridItem>
         </mx:GridRow>
         <mx:GridRow>
            <mx:GridItem>
               <mx:Label text="REST for Java Developers, Part 1"
                         styleName="articleTitle" />
            </mx:GridItem>
            <mx:GridItem>
               <mx:Label text="Brian Sletten" />
            </mx:GridItem>
            <mx:GridItem>
               <jw:UrlLabel id="restUrl"
                            text="http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html" />
            </mx:GridItem>
         </mx:GridRow>
      </mx:Grid>
   </mx:Panel>
</mx:Application>

Further refactoring

We can really simplify this application's code by refactoring out each GridRow used in the Grid into its own component. Listing 5 implements this in the custom ArticleGridRow component.

Listing 5. ArticleGridRow.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:GridRow xmlns:mx="http://www.adobe.com/2006/mxml"
            xmlns:jw="components.*">
   <mx:Script>
   <![CDATA[
   [Bindable] public var articleUrl:String = "N/A";
   [Bindable] public var articleTitle:String = "N/A";
   [Bindable] public var articleAuthor:String = "N/A";
   ]]>
   </mx:Script>

   <mx:GridItem>
      <mx:Label text="{articleTitle}" styleName="articleTitle" />
      </mx:GridItem>
      <mx:GridItem>
         <mx:Label text="{articleAuthor}" />
      </mx:GridItem>
      <mx:GridItem>
      <jw:UrlLabel text="{articleUrl}" />
   </mx:GridItem>
</mx:GridRow>

ArticleGridRow.mxml uses our previously created custom component, UrlLabel, and encapsulates other common functionality for a row in the grid as well. This compact component makes the regular application code much more manageable, as shown in Listing 6.

Listing 6. Example4.mxml

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                xmlns:jw="components.*"
                width="1100" height="700">
   <mx:Style>
   .articleTitle
   {
      font-weight: bold;
   }
   .gridLabel
   {
      font-weight: bold;
      font-size: 14;
   }
   </mx:Style>
   <mx:Panel title="Some of Dustin's Favorite JavaWorld Articles">
      <mx:Label text="A Small Sample of Favorite JavaWorld Articles"
                styleName="gridLabel" />
      <mx:Grid>
         <jw:ArticleGridRow articleTitle="Designing with Exceptions"
                            articleAuthor="Bill Venners"
                            articleUrl="http://www.javaworld.com/javaworld/jw-07-1998/jw-07-techniques.html" />
         <jw:ArticleGridRow articleTitle="Inheritance Versus Composition"
                            articleAuthor="Bill Venners"
                            articleUrl="http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html" />
         <jw:ArticleGridRow articleTitle="JSP Best Practices"
                            articleAuthor="Dustin Marx"
                            articleUrl="http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html" />
         <jw:ArticleGridRow articleTitle="More JSP Best Practices"
                            articleAuthor="Dustin Marx"
                            articleUrl="http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html" />
         <jw:ArticleGridRow articleTitle="Why Extends is Evil"
                            articleAuthor="Allen Holub"
                            articleUrl="http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html" />
         <jw:ArticleGridRow articleTitle="Why Getter and Setter Methods Are Evil"
                            articleAuthor="Allen Holub"
                            articleUrl="http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html" />
         <jw:ArticleGridRow articleTitle="New Features Added to Servlet 2.5"
                            articleAuthor="Jason Hunter"
                            articleUrl="http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html" />
         <jw:ArticleGridRow articleTitle="REST for Java Developers, Part 1"
                            articleAuthor="Brian Sletten"
                            articleUrl="http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html" />
      </mx:Grid>
   </mx:Panel>
</mx:Application>

The sample Flex application (Example 4.mxml) now:

  • Includes styling with CSS-compliant selectors
  • Demonstrates the MXML grammar
  • Demonstrates custom components
  • Even demonstrates a little ActionScript within the <mx:Script> tags in each custom component

This Flex application is still static in nature, however, in terms of both its data (statically provided in the MXML file) and user interaction. We still need to introduce Flex support for dynamically loading and presenting data.

Implementing dynamic features

The Flex Grid component we've used so far provides a static organization and representation of data similar to how an HTML table is often used. Unfortunately, Grid's static nature means that the user sees an unexciting, minimalist presentation of the data. Fortunately, Flex provides several types of data grids that are highly dynamic in nature. I'll focus here on the main DataGrid component that's delivered with the Flex SDK. (Flex also has an AdvancedDataGrid. Numerous third-party data grids are also available.)

The Flex 3 Language Reference (now at v3.2) and the Flex 3 Component Explorer both include example source code for visual components, along with a rendering of that same source code in the Web browser via the Flash Player. You can look at either document for example source code demonstrating how easy it is to apply the DataGrid component.

Listing 7 is a refactored version of our application that uses the DataGrid component in a manner similar to that shown in the Flex 3 Language Reference and in the Flex 3 Component Explorer.

Listing 7. Example5.mxml

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                xmlns:jw="components.*"
                width="1100" height="700">
   <mx:Style>
   .articleTitle
   {
      font-weight: bold;
   }
   .gridLabel
   {
      font-weight: bold;
      font-size: 14;
   }
   </mx:Style>
   <mx:XMLList id="articles">
      <article>
         <title>Designing with Exceptions</title>
         <author>Bill Venners</author>
         <url>http://www.javaworld.com/javaworld/jw-07-1998/jw-07-techniques.html</url>
      </article>
      <article>
         <title>Inheritance Versus Composition</title>
         <author>Bill Venners</author>
         <url>http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html</url>
      </article>
      <article>
         <title>JSP Best Practices</title>
         <author>Dustin Marx</author>
         <url>http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html</url>
      </article>
      <article>
         <title>More JSP Best Practices</title>
         <author>Dustin Marx</author>
         <url>http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html</url>
      </article>
      <article>
         <title>Why Extends is Evil</title>
         <author>Allen Holub</author>
         <url>http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html</url>
      </article>
      <article>
         <title>Why Getter and Setter Methods Are Evil</title>
         <author>Allen Holub</author>
         <url>http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html</url>
      </article>
      <article>
         <title>New Features Added to Servlet 2.5</title>
         <author>Jason Hunter</author>
         <url>http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html</url>
      </article>
      <article>
         <title>REST for Java Developers, Part 1</title>
         <author>Brian Sletten</author>
         <url>http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html</url>
      </article>
   </mx:XMLList>

   <mx:Panel id="mainPanel" title="Some of Dustin's Favorite JavaWorld Articles"
             width="{application.width}">
      <mx:Label text="A Small Sample of Favorite JavaWorld Articles"
                styleName="gridLabel" />
      <mx:DataGrid id="dataGrid"
                   width="{mainPanel.width*0.9}"
                   rowCount="5" dataProvider="{articles}"
                   editable="false">
            <mx:columns>
                <mx:DataGridColumn id="titleColumn"
                                   dataField="title"
                                   headerText="Article Title"
                                   width="{dataGrid.width*0.25}"
                                   editable="false" />
                <mx:DataGridColumn id="authorColumn"
                                   dataField="author"
                                   headerText="Article Author"
                                   width="{dataGrid.width*0.25}"
                                   editable="false" />
                <mx:DataGridColumn id="urlColumn"
                                   dataField="url"
                                   headerText="Article URL"
                                   editable="true">
                  <mx:itemRenderer>
                     <mx:Component>
                        <jw:UrlLabel />
                     </mx:Component>
                  </mx:itemRenderer>
                </mx:DataGridColumn>
            </mx:columns>
      </mx:DataGrid>
   </mx:Panel>
</mx:Application>

In Listing 7, the Grid has been replaced with the DataGrid. The DataGrid's data is loaded via its dataProvider attribute. The curly braces notation (dataProvider="{articles}") binds the DataGrid to the XMLList defined earlier in the MXML file. A nice side benefit is the natural separation of the data (now a standalone XMLList) from its presentation (DataGrid).

Compile Example5.mxml with mxmlc Example5.mxml. Its rendered SWF is shown in Figure 4.

Flex sample client with DataGrid
Figure 4. Flex sample client with DataGrid (Click to enlarge.)

As Figure 4 indicates, only five articles are shown at a time, in accordance with the number specified in the DataGrid's rowCount attribute. The row with focus in the DataGrid is highlighted. These rows of the DataGrid are also sortable and resizable, which is dynamic behavior this component supports out of the box. You can sort the rows by any particular column's values by clicking on that column's header. You can resize columns by clicking on the vertical lines between columns and dragging them either direction. Figure 5 shows this same application after the rows are sorted in reverse alphabetical order by article title and after the columns are resized.

DataGrid sorting and resizing
Figure 5. DataGrid sorting and resizing (Click to enlarge.)

Because the data associated with the DataGrid is no longer incorporated directly within the dynamically populated DataGrid as it was with the statically populated Grid, we no longer need the ArticleGridRow custom component created earlier. However, the UrlLabel custom component is still useful for adding HTML-like link functionality to the URLs displayed in the URL column.

Coupling Flex with Java EE

We have now refactored our source data out of the static Grid and have associated it via the dataProvider attribute with the DataGrid. However, we still have the data in hard-coded form in the MXML. Now you'll look at a version of the sample application that get this data from a server call instead. This is the most realistic use of Flex and demonstrates its power when coupled with Java EE. For this example, you'll still tie the DataGrid to a dataProvider named articles, but now the data that will be bound to the DataGrid will come from the server.

Flex offers HTTPService out of the box for communication with the server using HTTP. As you can see in Listing 8 -- the next version of the sample application -- HTTPService is remarkably easy to use in Flex and is especially well-suited to XML content.

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                xmlns:jw="components.*"
                width="1100" height="700"
                applicationComplete="articles.send();">
   <mx:Style>
   .articleTitle
   {
      font-weight: bold;
   }
   .gridLabel
   {
      font-weight: bold;
      font-size: 14;
   }
   </mx:Style>

   <mx:Script>
   import mx.controls.Alert;
   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;
   import mx.rpc.Fault;
   import mx.utils.ObjectUtil;

   public function faultHandler(event:FaultEvent):void
   {
      const fault:Fault = event.fault;
      const faultString:String =
           "FAULT CODE: " + fault.faultCode + "\n\n"
         + "FAULT DETAIL: " + fault.faultDetail + "\n\n"
         + "FAULT STRING: " + fault.faultString + "\n\n";
      Alert.show( "FAULT!\n\n" + faultString );
   }

   public function resultHandler(event:ResultEvent):void
   {
      trace("Response from service received.");
      // The ResultEvent includes a property "message" that contains
      //   the actual payload/contents of the result.  This is useful
      //   when no fault is occurring, but no data is being displayed
      //   in the component either.
   }
   </mx:Script>

   <mx:HTTPService id="articles"
                   url="http://localhost:8080/jwFlexJEEArticles/articles"
                   method="GET"
                   resultFormat="e4x"
                   fault="faultHandler(event);"
                   result="resultHandler(event);" />

   <mx:Panel id="mainPanel"
             title="Some of Dustin's Favorite JavaWorld Articles"
             width="{application.width}">
      <mx:Label text="A Small Sample of Favorite JavaWorld Articles"
                styleName="gridLabel" />
      <mx:DataGrid id="dataGrid"
                   width="{mainPanel.width*0.9}"
                   rowCount="5" dataProvider="{articles.lastResult.article}"
                   editable="false">
            <mx:columns>
                <mx:DataGridColumn id="titleColumn"
                                   dataField="title"
                                   headerText="Article Title"
                                   width="{dataGrid.width*0.25}"
                                   editable="false" />
                <mx:DataGridColumn id="authorColumn"
                                   dataField="author"
                                   headerText="Article Author"
                                   width="{dataGrid.width*0.25}"
                                   editable="false" />
                <mx:DataGridColumn id="urlColumn"
                                   dataField="url"
                                   headerText="Article URL"
                                   editable="true">
                  <mx:itemRenderer>
                     <mx:Component>
                        <jw:UrlLabel />
                     </mx:Component>
                  </mx:itemRenderer>
                </mx:DataGridColumn>
            </mx:columns>
      </mx:DataGrid>
   </mx:Panel>
</mx:Application>

The DataGrid portion of Listing 8 looks very similar to the DataGrid portion of the statically populated example in Listing 7. In fact, the only difference is the setting of the dataProvider attribute in the DataGrid component. That attribute is now set to point to the content returned by calls to the server via HTTPService. Here's the HTTPService specified in Listing 8:

<mx:HTTPService id="articles"
                   url="http://localhost:8080/jwFlexJEEArticles/articles"
                   method="GET"
                   resultFormat="e4x"
                   fault="faultHandler(event);"
                   result="resultHandler(event);" />

These simple lines of MXML code associate an HTTPService with an ID of articles to a URL indicated by the url attribute. The HTTP method to be used is GET, and results should be treated as ECMAScript for XML (E4X) format. The fault attribute specifies the ActionScript method to handle any faults associated with this HTTPService. The result attribute specifies the ActionScript method to be called upon successful response from the HTTPService. These two specific ActionScript methods -- faultHandler() and resultHandler() -- are spelled out in the <mx:Script> portion of the MXML file.

Now that we have an HTTPService associated with a URL, we need to put something behind that URL -- a simple Java HTTP servlet for this example. In many situations, the data might be retrieved from the database or might be collated from a variety of sources. To enable a simpler example, the data will be simply read from an external XML file and will not need any special parsing or collating. It can be returned to the Flex client as-is. The important point is the communication between Flex and the enterprise Java server.

Listing 9 shows the servlet code defined in ArticleServer.java.

Listing 9. ArticleServer.java

package javaworld.dustin;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Simplistic servlet to serve up basic article information.
 * 
 * @author Dustin
 */
public class ArticleServer extends HttpServlet
{
   /** 
    * Handles the HTTP <code>GET</code> method.
    * 
    * @param request Servlet request.
    * @param response Servlet response.
    * @throws ServletException
    * @throws IOException
    */
   @Override
   protected void doGet(
      final HttpServletRequest request, final HttpServletResponse response)
      throws ServletException, IOException
   {
      response.setContentType("text/html;charset=UTF-8");
      final PrintWriter out = response.getWriter();
      try
      {
         final ServletContext context = this.getServletContext();
         final InputStream articlesXmlInput =
            context.getResourceAsStream("/Articles.xml");
         final InputStreamReader reader = new InputStreamReader(articlesXmlInput);
         final BufferedReader bufferedReader = new BufferedReader(reader);
         String line = null;
         while ((line = bufferedReader.readLine()) != null)
         {
            out.println(line);
         }
      }
      finally
      { 
         out.close();
      }      
   } 

   /** 
    * Handles the HTTP <code>POST</code> method.
    * @param request servlet request
    * @param response servlet response
    * @throws ServletException
    * @throws IOException
    * @throws UnsupportedOperationException Thrown if this method is called
    *    because this method is not intended for clients to call.
    */
   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException
   {
      throw new UnsupportedOperationException(
         "Only GET HTTP method applies for this service.");
   }

   /** 
    * Returns a short description of the servlet.
    */
   @Override
   public String getServletInfo()
   {
      return "Provides basic information on select JavaWorld articles via GET request.";
   }
}

This simple servlet reads Articles.xml from the Web application classpath and returns the read-in XML to the caller. The Articles.xml file, shown in Listing 10, should look familiar at this point because it is merely a close adaptation of the XML previously embedded directly within the Flex MXML code.

Listing 10. Articles.xml

<?xml version="1.0" encoding="UTF-8"?>
<Articles>
   <article>
      <title>Designing with Exceptions</title>
      <author>Bill Venners</author>
      <url>http://www.javaworld.com/javaworld/jw-07-1998/jw-07-techniques.html</url>
   </article>
   <article>
      <title>Inheritance Versus Composition</title>
      <author>Bill Venners</author>
      <url>http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html</url>
   </article>
   <article>
      <title>JSP Best Practices</title>
      <author>Dustin Marx</author>
      <url>http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html</url>
   </article>
   <article>
      <title>More JSP Best Practices</title>
      <author>Dustin Marx</author>
      <url>http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html</url>
   </article>
   <article>
      <title>Why Extends is Evil</title>
      <author>Allen Holub</author>
      <url>http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html</url>
   </article>
   <article>
      <title>Why Getter and Setter Methods Are Evil</title>
      <author>Allen Holub</author>
      <url>http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html</url>
   </article>
   <article>
      <title>New Features Added to Servlet 2.5</title>
      <author>Jason Hunter</author>
      <url>http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html</url>
   </article>
   <article>
      <title>REST for Java Developers, Part 1</title>
      <author>Brian Sletten</author>
      <url>http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html</url>
   </article>
</Articles>

Listing 11 shows the web.xml file for the servlet.

Listing 11. Servlet web.xml

<?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>JavaWorld Flex/Java EE Part 1 Article</display-name>
   <description>Server-side functionality for JavaWorld Flex/Java EE Part 1 Article</description>
   <servlet>
      <servlet-name>ArticleServer</servlet-name>
      <servlet-class>javaworld.dustin.ArticleServer</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>ArticleServer</servlet-name>
      <url-pattern>/articles</url-pattern>
   </servlet-mapping>
   
   <session-config>
      <session-timeout>35</session-timeout>
   </session-config>
   <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>

As you'd expect from the URL used in the Flex application's HTTPService url attribute, this web.xml file exposes the servlet with the URL pattern of /articles. The MXML HTTPService URL is set to http://localhost:8080/jwFlexJEEArticles/articles. The host and port portions of the URL come from whatever machine and port the Web server hosting this servlet is running on. The jwFlexJEEArticles portion of the URL is the Web context associated with the deployment of this servlet.

With a WAR built with the servlet defined in Listing 9, the Articles.xml file defined in Listing 10, and the web.xml Web application descriptor file in Listing 11, it is time to deploy that WAR. I am deploying the WAR to GlassFish, but you could deploy it to any Java EE-compliant Web server (such as Tomcat).

When the WAR is deployed, you can run the Flex application again. The MXML includes a call in its Application root element (via its applicationComplete attribute) to a method that invokes the HTTP servlet as soon as the Flex application is initialized. This means that the Flex application loads up and then immediately calls the servlet and renders the output from the servlet. When I first execute the Flex application, I see the display shown in Figure 6.

Security error without crossdomain.xml file
Figure 6. Security error without crossdomain.xml file (Click to enlarge.)

The fault handler defined in the example has been invoked. The Alert is a pop-up that displays the three pieces of fault information as instructed. In this case, the fault is a result of our Flex application trying to access a server running on a different domain. To remedy this, a crossdomain.xml file must be added to the server's root directory. The most wide-open crossdomain.xml file looks like this:

<?xml version="1.0"?>
<!-- crossdomain.xml for JavaWorld Flex/Java EE Examples. -->
<cross-domain-policy>
    <allow-access-from domain="*" />
</cross-domain-policy>

The asterisk could be replaced with actual domains to narrow down the domains from which a client can access this server's functionality.

With the crossdomain.xml file in place, the Flex application is executed again and looks like Figure 7 now:

Flex application populated via Java servlet
Figure 7. Flex application populated via Java servlet (Click to enlarge.)

The Flex application appears exactly the same as when the data was hard-coded into the MXML file, but now that data has been retrieved dynamically from the server.

Here's a summary of the steps required to access an HTTP service from a Flex client:

  1. Add a simple HTTPService element in the MXML file.
  2. Associate the HTTPService with the DataGrid via the DataGrid's dataProvider attribute.
  3. Invoke the HTTPService with its send() method.
  4. Catch any faults or problems with the service call in a defined fault-handler method and handle results with a defined results-handler method.

Note that we did not need to write any code whatsoever to explicitly parse the XML provided by the servlet to the Flex client. By specifying the result format as E4X, we leveraged Flex's support for E4X to bind the DataGrid columns to the XML elements and were freed from any manual XML parsing.

Conclusion to Part 1

Flex is one of the relatively rare frameworks that improves both the developer's experience and the end user's. This article has introduced Flex as a client-side technology for enterprise Java applications. I have focused on using Flex's HTTPService to communicate with an enterprise Java back-end. In Part 2, I'll introduce Flex's WebService class, introduce the RemoteObject class, show how to use BlazeDS to provide a proxy on the server side, and describe other types of communication between a Flex client and an enterprise Java server.

 

Flex adoption in the Java community

Only recently has Flex seemed to catch the interest of mainstream Java developers. One barrier to acceptance has been the misperception that the Flex runtime environment (the Flash Player) is only good for games, movies, and other "flashy" applications. I certainly thought for a long time that Flash was not a runtime for productivity-oriented applications -- and that Flash applications were only written with drag-and-drop tools such as Flash Professional or Creative Studio.

I started to think about Flex differently after reading Bruce Eckel's "Hybridizing Java" in 2007. Eckel outlined many common frustrations with the available UI technologies for Java. He discussed some of Flex's merits and the advantages of using it with Java on the back-end. Since then, several other prominent Java developers have adopted Flex. Key Java client-side developers have moved from Sun to Adobe to work on and with Flex. And many everyday Java developers -- my colleagues, people I've met at conferences, and fellow bloggers -- have expressed their satisfaction with quickly learning and applying Flex in their daily work.

Actions by Adobe have also helped to boost Flex's acceptance by Java developers. When Adobe stopped charging license fees for the Flex SDK and the command-line compiler and debugger, the potential audience for Flex grew exponentially. Adobe later released the Flex 3 SDK, compiler, and debugger as open source. BlazeDS was released as open source shortly before the Flex 3 release. (The Flash Player itself may never be open source, but it's now possible for an open source project to develop an alternative player, thanks to the Adobe-led Open Screen Project.)

A recent announcement that SpringSource will collaborate with Adobe to provide Spring BlazeDS Integration is more evidence of Flex's rising popularity. Although BlazeDS and GraniteDS currently provide some support for integrating Flex and the Spring Framework, the Spring BlazeDS Integration product promises to make working with Flex even easier for Spring users. All of these factors suggest that Flex is on the rise as a front-end alternative for Java-based developers.

Dustin Marx is a principal software engineer and architect at Raytheon Company. His previous published work for JavaWorld includes "More JSP best practices" (July 2003) and "JSP Best Practices" (November 2001).

Learn more about this topic

Download the Flex SDK, including the command-line compiler and debugger. A single zip file contains the Flex SDK for multiple platforms.

More from JavaWorld

Join the discussion
Be the first to comment on this article. Our Commenting Policies