Skip to main content

Web Service Programming for the Masses, Part II: Developing the RIA

Posted by stoicflame on January 9, 2008 at 11:41 AM PST

The first part of this tutorial walked through the development of a Web service API that exposes its endpoints via SOAP, REST/XML, JSON, GWT-RPC, and AMF. In this second part of the tutorial, we intend to prove out this API. We will walk through building a rich AJAX application with an embedded Flash component. The AJAX application will use
GWT-RPC to access the API while the Flash movie will use
AMF.

For those of you who would like to see the example code on your local filesystem, you can check it out of SVN at http://svn.codehaus.org/enunciate/trunk/enunciate/src/samples/addressbook/.

Step 1: Set up the GWT application.

We first have to create our GWT application. We will use the name “net.java.apps.addressbook.AddressBookApp” to identify our application to Enunciate and
GWT.

Before we continue, we have to add a project dependency on the main GWT client-side library. The added dependency declaration will go in the “dependencies”
section of our pom.xml file:

<dependency>
  <groupId>com.google.gwt</groupId>
  <artifactId>gwt-user</artifactId>
  <version>1.4.60</version>
</dependency>

Then, we create our main GWT application structure. For more information, see the GWT documentation:

addressbook/src/main/gwt/net/java/apps/addressbook/AddressBookApp.gwt.xml:
<module>

  <!--inherit from the main GWT client-side library.-->
  <inherits name='com.google.gwt.user.User'/>

  <!--inherit from our Enunciate-generated GWT-RPC client-side code.-->
  <inherits name='net.java.ws.addressbook.AddressBookGWT'/>

  <!--define the entry point for the address book app.-->
  <entry-point class='net.java.apps.addressbook.client.AddressBookApp'/>

</module>
addressbook/src/main/gwt/net/java/apps/addressbook/public/addressbook.html:
<html>
<head>
  <title>AddressBook Application</title>
  <meta name='gwt:module' content='net.java.apps.addressbook.AddressBookApp'>
</head>
<body>
  <script language="javascript" src="gwt.js"></script>
  <iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
  <h1>AddressBook Application</h1>
</body>
</html>
addressbook/src/main/gwt/net/java/apps/addressbook/client/AddressBookApp.java:
package net.java.apps.addressbook.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.*;

/**
* Basic entry point for the GWT address book application.
*
* @author Ryan Heaton
*/
public class AddressBookApp implements EntryPoint {
  public void onModuleLoad() {
  }
}

Finally, we edit the Enunciate configuration file to point to our application so it will be compiled during the normal compile process.

addressbook/enunciate.xml:
<enunciate>
  <modules>
    <gwt disabled="false" gwtHome="/path/to/gwt/home" rpcModuleName="net.java.ws.addressbook.AddressBookGWT">
      <app srcDir="src/main/gwt">
        <module name="net.java.apps.addressbook.AddressBookApp"/>
      </app>
    </gwt>
    <amf disabled="false" flexHome="/path/to/flex/sdk/home"/>
  </modules>
</enunciate>

Step 2: Set up the Flex Application

Having created the skeleton for our AJAX application using GWT, we can now set up the skeleton for developing our Flex application. This takes only a single
MXML file and editing the Enunciate configuration file to point to our Flex source directory. For more information, see Adobe's documentation on developing a
Flex application.

addressbook/src/main/flex/net/java/apps/addressbook/bytype.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#FFFFFF">
</mx:Application>
addressbook/enunciate.xml:
<enunciate>
  <modules>
    <gwt disabled="false" gwtHome="/path/to/gwt/home" rpcModuleName="net.java.ws.addressbook.AddressBookGWT">
      <app srcDir="src/main/gwt">
        <module name="net.java.apps.addressbook.AddressBookApp"/>
      </app>
    </gwt>
    <amf disabled="false" flexHome="/path/to/flex/sdk/home">
      <app srcDir="src/main/flex" name="bytype" mainMxmlFile="src/main/flex/net/java/apps/addressbook/bytype.mxml"/>
    </amf>
  </modules>
</enunciate>

*At this point, it might be convenient to rebuild our project file using Maven. The new project will contain references to the new generated code and to our
GWT application.

Step 3: Write the Flex Application

We can now write our Flash component that will lookup contacts by type. Our application has only three components: a href="http://livedocs.adobe.com/flex/201/langref/mx/controls/DataGrid.html">DataGrid to display the information, a
ComboBox used to select the contact type, and a Button that will invoke the find and populate the DataGrid with the data. The
Script tag is where we put our
logic to invoke the AMF Web service method:

addressbook/src/main/flex/net/java/apps/addressbook/bytype.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#FFFFFF" creationComplete="initApp()">
  <mx:Script><![CDATA[
     import mx.controls.Alert;
     import mx.rpc.events.FaultEvent;
     import mx.rpc.events.ResultEvent;
     import mx.rpc.remoting.RemoteObject;
     import net.java.ws.addressbook.services.AddressBook;
     import net.java.ws.addressbook.services.AddressBook.FindContactsByTypeResultEvent;
     import net.java.ws.addressbook.domain.ContactType;
     import net.java.ws.addressbook.domain.ContactList;
     import net.java.ws.addressbook.domain.Contact;

     //instantiate our address book service.
     var book:AddressBook = new AddressBook();

     //array of contact types; used to popluate the combo box.
     [Bindable]
     public var contactTypes: Array = [ ContactType.friend, ContactType.family, ContactType.professional ];

     private function initApp():void {
       //initialize our address book with our event listeners.
        book.addEventListener("fault", myFaultHandler);
        book.addEventListener("findContactsByType", myEventHandler);
     }

     private function myFaultHandler(event:FaultEvent):void {
       //if a fault occurs, display a fault message.
       Alert.show(event.fault.message);
     }

     private function myEventHandler(event:FindContactsByTypeResultEvent):void {
       //the event listener is a strongly-typed listener for the findContactsByType method.
       dg.dataProvider = event.result.contacts;
     }

  ]]></mx:Script>

  <mx:DataGrid id="dg" width="100%" height="100%"/>

  <mx:ComboBox id="ct" dataProvider="{contactTypes}"/>

  <mx:Button label="Find By Type" click="book.findContactsByType(ct.selectedItem as String)"/>

</mx:Application>

You'll notice that the remote interface that we're working with in our Flex application is strongly typed (inasmuch as Flex allows it). This is because
Enunciate generates the client-side ActionScript classes that are used to access the AMF endpoints (these can be found in
addressbook/target/enunciate-generate/amf/client). This is a big advantage over using an ActionScript href="http://livedocs.adobe.com/flex/201/langref/mx/rpc/remoting/mxml/RemoteObject.html">RemoteObject directly since you get compile-time type
safety.

Step 4: Write the GWT Application

Our GWT Application will be a tab-based UI, with two tabs. The first tab will be a lookup of contacts by name via suggestion box. The second tab will display
our Flash component that looks up contacts by type. Because we are using GWT, we have the advantage of writing the AJAX UI in Java. We'll let Enunciate handle
the process of invoking GWT to compile our Java source code into JavaScript. Since this isn't a tutorial on GWT, we'll let the code do the talking.

addressbook/src/main/gwt/net/java/apps/addressbook/client/AddressBookApp.java:
package net.java.apps.addressbook.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.*;
import net.java.ws.addressbook.client.services.AddressBook;
import net.java.ws.addressbook.client.domain.ContactList;
import net.java.ws.addressbook.client.domain.Contact;

import java.util.Iterator;
import java.util.Collection;
import java.util.ArrayList;

/**
* Basic entry point for the GWT address book application.
*
* @author Ryan Heaton
*/
public class AddressBookApp implements EntryPoint {
  public void onModuleLoad() {
    //the panel that will hold the widgets that will look up by name.
    VerticalPanel byNamePanel = new VerticalPanel();

    //the Enunciate-generated client-side GWT address book service
    final AddressBook book = new AddressBook();

    //the grid we will use to display the contacts that are found.
    final Grid contactGrid = new Grid();

    //the oracle that will make a remote call to populate the suggest box.
    SuggestOracle oracle = new SuggestOracle() {
      public void requestSuggestions(final Request request, final Callback callback) {
        String query = request.getQuery();

        //call the method to find contacts by name.
        book.findContactsByName(query, new AddressBook.FindContactsByNameResponseCallback() {
          public void onResponse(ContactList response) {
            Iterator contactIt = response.getContacts().iterator();
            Collection suggestions = new ArrayList();
            while (contactIt.hasNext()) {
              final Contact contact = (Contact) contactIt.next();

              //add the suggestion.
              suggestions.add(new Suggestion() {
                public String getDisplayString() {
                  return contact.getName();
                }

                public String getReplacementString() {
                  return contact.getName();
                }
              });
            }

            contactGrid.clear(); //clear the grid.
            callback.onSuggestionsReady(request, new Response(suggestions));
          }

          public void onError(Throwable throwable) {
            //do nothing if an error occurs while asking for suggestions
          }
        });
      }
    };

    //the suggest box (instantiated with our oracle).
    final SuggestBox suggestBox = new SuggestBox(oracle);

    //The panel that will hold the suggest box and the "find" button.
    HorizontalPanel findForm = new HorizontalPanel();
    findForm.add(suggestBox);

    //the "find" button.
    Button findButton = new Button("find");
    findButton.addClickListener(new ClickListener() {
      public void onClick(Widget widget) {
        //when "find" is clicked, make the query and populate the grid.
        String text = suggestBox.getText();
        book.findContactsByName(text, new AddressBook.FindContactsByNameResponseCallback() {
          public void onResponse(ContactList response) {
            contactGrid.resize(6 * response.getContacts().size(), 2);
            Iterator contactIt = response.getContacts().iterator();
            int i = 0;
            while (contactIt.hasNext()) {
              Contact contact = (Contact) contactIt.next();
              contactGrid.setWidget(++i, 0, new Label("Name:"));
              contactGrid.setWidget(i, 1, new Label(contact.getName()));
              contactGrid.setWidget(++i, 0, new Label("Phone:"));
              contactGrid.setWidget(i, 1, new Label(contact.getPhone()));
              contactGrid.setWidget(++i, 0, new Label("Address:"));
              contactGrid.setWidget(i, 1, new Label(contact.getAddress1()));
              contactGrid.setWidget(++i, 0, new Label("City:"));
              contactGrid.setWidget(i, 1, new Label(contact.getCity()));
              contactGrid.setWidget(++i, 0, new Label("Type:"));
              contactGrid.setWidget(i, 1, new Label(contact.getContactType()));
              contactGrid.setWidget(++i, 0, new HTML("<hr/>"));
              contactGrid.setWidget(i, 1, new HTML("<hr/>"));
            }
          }

          public void onError(Throwable throwable) {
            //if an error while doing a "find," display it in the grid.
            contactGrid.resize(1, 1);
            contactGrid.setWidget(0, 0, new Label("ERROR: " + throwable.getMessage()));
          }
        });
      }
    });

    //add the find button.
    findForm.add(findButton);

    //add the find form to the panel.
    byNamePanel.add(findForm);

    //add the display grid to the panel.
    byNamePanel.add(contactGrid);

    //create the tab panel.
    TabPanel panel = new TabPanel();

    //add the find by name panel to the tab panel.
    panel.add(byNamePanel, "&nbsp;<a href="#byname">by name</a>&nbsp;", true);

    //create some HTML that will embed our flash component.
    HTML flashHTML = new HTML("<object width="550" height="400">\n" +
              "<param name="movie" value="bytype.swf">\n" +
              "<embed src="bytype.swf" width="550" height="400">\n" +
              "</embed>\n" +
              "</object>");

    //add the flash component to the other tab in the tab panel.
    panel.add(flashHTML, "&nbsp;<a href="#bytype">by type</a>&nbsp;", true);

    //add the tab panel to the root HTML.
    RootPanel.get().add(panel);
  }
}

You'll notice that Enunciate has also generated client-side code for accessing our Web service API via GWT. Again, this adds significant benefits in terms of
type safety and simplifies the creation of the initial environment.

Step 5: Build, Package, and Run the Application

Once again, we run

mvn jetty:run-war

Assuming everything builds correctly, we can navigate to
http://localhost:8080/addressbook/addressbook.html and see our Rich AJAX Application with an
embedded Flash component, both of which make dynamic calls to our Web service API.

Comments

The FindContactsByTypeResultEvent is missing in this tutorial

First of all, this is a great example to follow to get familiar with enunciate. However, I met bytype.mxml(32): Error: Type was not found or was not a compile-time constant: FindContactsByTypeResultEvent. after I run mvn jetty:run-war.

Re: The FindContactsByTypeResultEvent is missing in this tutoria

Hmm... The FindContactsByTypeResultEvent object is generated by Enunciate before the flex compile should be invoked. I'm not sure why that's not happening in your case.

When I checkout that example and do "mvn -Dgwt.home=/path/to/gwt/home -Dflex.home=/path/to/flex/sdk/home clean jetty:run-war" it works for me.  I'm on java version "1.6.0_17" and Apache Maven 2.2.1.