Abstract

You're on the road meeting a client at a coffee shop, flip open your laptop, and notice that there's no internet connection. This doesn't stop you connecting to your Force.com web application and viewing account and contact information though, much to your client's obvious surprise. While your end users have been able to use Salesforce Offline Edition to access CRM data in this disconnected way, now developers can use new offline technologies to make any of their on-demand applications work off-line.

Gears.jpg

Google Gears lets you create Web applications that allow you to perform this conjuring trick. You can access these Web applications just as you ordinarily would, even though your internet connection is down. That's because Google Gears is an open source browser extension that lets you create web applications that can run offline. It replaces the dreaded "404 page not found" message with a local database, a local cache and more.

This tutorial shows how to create such an application. Along the way we demonstrate Google Gears, Google Web Toolkit (to build a nice Ajax interface), and the use of Apache Axis to access Force.com through its remote Web services API. The source code for the application is available, as well as screencast showing it in action.

Introduction: Cloud Computing Goes Offline

Your company has a Web application that exposes some Force.com data. How do you access this Web application when you're not online? Well, surprisingly the answer is no longer "you can't", as the recent hype around Google Gears has shown. What Google Gears does is provide an extension to your browser. This extension includes a tiny database engine, a cache, and a JavaScript library that lets your Web application make use of the database and cache even when not connected to the Web.

In effect, your browser can cache relevant Web application pages and data, and let you continue to navigate the Web application even when you're not online. You don't get the infamous " 404 page. A great example of this is implemented in Google's own RSS reader. As shown in Figure 1, it now provides a facility to "go offline."


Googlereader.gif
Figure 1: Google Reader and its offline mode


When you click the icon, your Web browser effectively downloads your unread feed subscriptions and stores them in the local browser database. It also caches the relevant pages of the Google Reader Web application. You can then pull the plug on your internet connection, and still continue to use Google Reader as if you were online. Moreover, when you do go back online Google Reader will sync back with the server, marking those items that you read while offline, as read.

Software as a service traditionally dictates that the service be always available—especially as we move towards the Web-as-a-platform paradigm. Google Gears allows us to be more flexible, to support the occasionally connected users, to provide a rich offline experience. Note that Google Gears is not the only technology that allows you to do this—another that springs to mind is Adobe AIR

What We're Going To Build

This tutorial shows how to create a Web application that exposes Force.com data, and how to extend the Web application with Google Gears so that it can be used offline.

We're going to use Java as our programming language. Apache Axis will be used to create the Web services integration with Force.com, and we'll show how easy it is to query your Force.com data using this Web service interface.

We're going to use Google Web Toolkit to create our Web application. This innovative Ajax-based framework lets you create your Web application purely in Java. The toolkit compiles your Java code down to JavaScript for you, which means that you aren't exposed to all the ugly JavaScript manipulation traditionally necessary to get this kind of stuff working.

Finally, we're going to use the database component of Google Gears to store information so that the application can still access information when offline.

Note that this application requires an explicit signal that you're going offline. For a better user experience, consider extending it with continuous background synchroization.

There are a lot of technologies here and no convenient order in which to introduce them all. We hope that this tutorial and the accompanying source code will help you piece it all together.

What You Need To Get Started

Besides your Developer Edition login, you will need:

  • Google Gears - We'll use this to add offline capabilities
  • Google Web Toolkit - Version 1.4 RC 1. We'll use this to provide the user interface (UI) for our application
  • Google API Library - This provides the Google Gears/Web Toolkit integration
  • Apache Axis - Version 1.4. This provides the web services integration stack

Go and download the bits now if you want to follow along. You'll probably also want to download the source code for this tutorial as we haven't put all the code in the tutorial itself. The appendices contain additional information on how to create and deploy the application yourself.

Our Goal

We are going to build a simple Web application, as shown in Figure 2. There's nothing too fancy: two buttons, a list of accounts, and a description panel.


Demo.gif
Figure 2: Screenshot of the application in action within a browser window. Click to see the application in action .

The idea is that when you hit the "Fetch Accounts" button, the application uses the web services stack to fetch your Account data and populate the list panel. When you subsequently click on an account, the application again uses the web services stack to remotely fetch Contact details for the selected account. To add a slight twist, the application uses Ajax so that when you do click on an account, the description panel is updated without refreshing the entire page.

The real magic happens when hit the "Go Offline" button. Once you do this, you can literally pull the internet connection and still continue to click on accounts. Contact details will still be shown. In this case, the application will query a local database of Account/Contact information instead. This database will have been populated as soon as you hit "Go Offline".

Application Demo

Watch the Force.com application with Google Gears and Google Web Toolkit demo.

Our Application Architecture

Applications that are built with the capability of going offline tend to be architected in a way that centralizes data access. Think about Figure 2. When you click on an account, the Contact details for that account have to be displayed. However, if you're online the details should be fetched from Force.com, and if you're offline they should be fetched from the Google Gears local database in the browser. In other words, clicking on an account should call a method that does one of two things, depending if you're online or offline. We call this a Data Switch. Figure 3 shows the typical architecture.


Ggarch.jpg
Figure 3: Our application architecture (From Google Gears documentation)

After the Data Switch has determined whether to fetch the data remotely or locally, it needs to actually fetch the data of course. We package all the code dealing with the remote server data layer (our Apache Axis-based implementation) in a Server Data Layer, and the code dealing with local access (implemented using Google Gears) in a Local Data Layer.

The key here is understanding that the Application UI, the Data Switch, the Local Data Layer and the Database will all execute in your browser. The Server Data Layer, while it sits in your browser, will also makes calls over the internet to your server (which in turn makes calls to Force.com). In other words, the Server Data Layer makes remote procedure calls to a server component, and has to exchange information with the server.

There's a lot here! In the following sections we're going to start by describing the Application User Interface. Every now and then you'll see references to the Data Switch and Data Layers from within the code snippets—hang in there. After describing the Application UI, we'll describe the Server Data Layer, RPC functionality and data serialization. We'll then describe the local data layer which makes use of Google Gears. Finally, we'll describe the main program entry point and the Data Switch.

Creating the User Interface with Google Web Toolkit

The great thing about the GWT is that it allows you to create Ajax application without writing any HTML or JavaScript. This section quickly runs through the main components used in our example application that pertain to the user interface.

All of the code in the following subsections executes on the browser. Although it's all presented as Java here, the GWT translator converts it all to JavaScript, which is subsequently executed on the browser.

The Account Details Panel

As an example of this type of programming, here's the code that creates the Account Details Panel, the bottom panel shown in Figure 2.

public class AccountDetails extends Composite {

    final VerticalPanel mainPanel = new VerticalPanel();
    final VerticalPanel headerPanel = new VerticalPanel();
    final HTML detailHTML = new HTML();

    public AccountDetails() {
        /* We style the header panel */
        headerPanel.setStyleName("account-DetailHeader");
        headerPanel.add(new HTML("Description of Account:"));

        /* Main panel contains header and detail */
        mainPanel.add(headerPanel);
        mainPanel.add(detailHTML);

        initWidget(mainPanel);
        setStyleName("account-Detail");
    }

     /* Called to display the details of an account */
    public void showDetailsForAcccount(AccountDetailsInfo details) {
        StringBuffer out = new StringBuffer();
        out.append("<b>Account ID: </b> " + details.getAccountNumber()
                + "<br />");
        out.append("<b>Contacts: </b><br /><table class='contacts'>");
        for (int i = 0; i < details.getContacts().size(); i++) {
            ContactInfo ci = (ContactInfo) details.getContacts().get(i);
            out.append("<tr><td>" + ci.getContactName() + "</td><td>"
                    + ci.getContactPhone() + "</td></tr>");
        }
        out.append("</table>");
        detailHTML.setHTML(out.toString());   
    }
}

This should be pretty easy to read. The Composite class indicates that this is a GWT component composed of other components. We're using VerticalPanel and HTML components inside. The first panel, headerPanel simply holds the text "Description of Account". Note that the setStyleName() method is used to set the CSS style of the generated HTML. This allows you to effectively change the look and feel of the application by simply modifying an external CSS file. The mainPanel will be used to display the Contact information for an account. This type of programming is very similar to Java Swing UI construction - you simply use the built-in components to construct other components.

The constructor creates our AccountDetails widget, organizing the panels and widgets as we see fit. The showDetailsForAccount() method simply generates an HTML table showing Contact information for a given account. This will be called when a user selects an account.

Note that when displaying data on the browser, as we do here in detailHTML.setHTML(), that you should always be aware of where the data has come from and what it may contain. For example, this call will result in setting the innerHTML property of the HTML widget in JavaScript. If the argument contained some nasty JavaScript instead of HTML, the result could be a cross-site scripting security hole. You should always quote or escape data that is displayed. For example, you can use StringEscapeUtils (Lang 2.3 API) found in Apache Commons to do this.

The Account List Panel

The panel that lists all accounts is similarly constructed. Here are the important bits:

public class AccountList extends Composite implements TableListener {

    /* The GWT component we use to render the table */
    private FlexTable table = new FlexTable();

    public void populate(AccountInfo[] c) {
       for (int i = 0; i < c.length; i++) {
        AccountInfo contact = c[i];
        setText(i, 1, contact.getName());
        setText(i, 0, contact.getAccountNumber());
       }
    }

    public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
       /* ... */
       MyApplication.get().displayAccountDetails(accountNumberOfSelectedRow);
    }
}

The populate() method will be called to show the list of Accounts in the table - essentially after we've fetched them when the user clicks the "Fetch Accounts" button. The onCellClicked() method determines the account number of the row being clicked on, and then calls the displayAccountDetails() method.

What's interesting is that we had to factor out the displayAccountDetails() method. If you're online, it can simply go and fetch the account details. When you're offline, it has to fetch the details from a local database. In essence, this is our implementation of a Data Switch, something we'll come back to later.

The Menu Bar

The top menu bar contains two buttons:

public class MenuBar extends HorizontalPanel {

final Button buttonFetch = new Button("Fetch Accounts");
final Button buttonOffOn = new Button("Go Offline");    

/* ... */

}

Here's our code for handling the buttonOffOn functionality:

buttonOffOn.addClickListener(new ClickListener() {
  public void onClick(Widget sender) {
    Button s = (Button) sender;
    if (s.getText().equals("Go Offline")) {
        s.setText("Go Online");
        localDataLayer.goOffLine();
        MyApplication.get().setOnline(false);
    } else {
        s.setText("Go Offline");
        MyApplication.get().setOnline(true);
    }

  }
});

As you can see, buttons are programmed by writing listeners (implementations of the ClickListener interface), whose onClick() method will get called when the button is pushed. It's important to remember that this code will eventually be executed on the client, not on the server. As you can see, what the code does is change the button text whenever the button is pushed, and record our current state. Moreover, if you go offline it calls dataLayer.goOffline() (to fetch the data so that we can continue to function offline). This introduces another typical architectural feature of these types of applications (See Figure 3) - a Local Data Layer that allows you to store and retrieve information locally.

If you push the buttonFetch button, we need to fetch the remote Account information and populate the Account List Panel that we saw earlier. Here's the code:

buttonFetch.addClickListener(new ClickListener() {
   public void onClick(Widget sender) {
    serverDataLayer.getAccounts(new AsyncCallback() {

        public void onSuccess(Object result) {
            buttonFetch.setEnabled(false);
            AccountInfo[] c = (AccountInfo[]) result;
            accountList.populate(c);
            buttonOffOn.setEnabled(true);
        }

        public void onFailure(Throwable caught) {
            System.err.println("Failure " + caught.getMessage());
        }
    });
   }
 });

The interesting action lies in the sfService.getAccounts() method. When you push the button, onClick() will call this method to go and fetch the Account information from the remote server. However, this is effectively a call from the client (the browser) to the server and so GTW ensures that this happens asynchronously--you don't want to freeze the user interface whenever any action is carried out. The onSuccess() and onFailure() will be called depending on the success of this asynchronous call back to the server, which we'll investigate in the RPC section below. If we are successful, onSuccess() takes the list of accounts and populates our Account List Panel using the populate() method shown above.

Creating the Server Data Layer with Apache Axis

The client application, effectively the GWT page running on the browser, needs to call back to our server to fetch data to display. Our server, in turn, will call Salesforce.com. GWT implements a seamless remote procedure call (RPC) protocol that allows us to do this. You really only need three things to get going:

  • You need to define the interfaces of the service
  • You need to define the data types and ensure that they are serializable
  • You need to implement the service

Let's look at each of these in turn.

Defining the Interfaces

First, let's define the interface of the remote service:

public interface SFService extends RemoteService {

    /* Fetch all accounts */
    AccountInfo[] getAccounts();

    /* Fetch details for a particular account */
    AccountDetailsInfo getAccountDetails(String accountNumber);

    /* Retrieve all the contact information for all the accounts */
    /* Used when we go offline */
    ContactInfo[] getAllContacts();
}

Note how the interface extends the GWT RemoteService. We'll be using the getAccounts() method to populate the account information in the Account List panel. When you're online, every time you click on an account, the getAccountDetails() method is called to fetch the details of an account, which is then displayed. Finally, the getAllContacts() method is called before you go offline, to retrieve all the information needed to run in offline mode.

GWT also demands that you create an asynchronous version of the interface:

public interface SFServiceAsync {
  void getAccounts(AsyncCallback callback);
  void getAccountDetails(String accountNumber, AsyncCallback callback);
  void getAllContacts(AsyncCallback callback);
}

As you can see, you simply need to ensure all return values are void, and that you include an AsyncCallback parameter.

You've already seen the getAccounts() method in action in the implementation of the ClickListener for buttonFetch. As you can see, the AsycnCallback interface simply has a method that is invoked on a successful call, and one that is invoked on a failure.

Defining Serializable Classes

The methods in our remote service interface, such as getAccounts(), return data. Since the service will actually live on our server, and it will be called from our button handler which lives on the browser, GWT needs to marshal the data before it can be transmitted.

To make your classes serializable they need to implement the IsSerializable interface and adhere to GWT's rules. These rules permit all primitive types, primitive type wrappers, arrays and some collection types.

Our types are pretty simple. Here's a snippet from our AccountInfo class:

public class AccountInfo implements IsSerializable {

    private String name;
    private String accountNumber;
    private String accountDescription;

    public AccountInfo() {};

    public AccountInfo(String name, String description, String acctNumber) {
        this.name = name;
        this.accountNumber = acctNumber;
        this.accountDescription = description;
    }

    public String getName() {
        return name;
    }

    /* ... */
}

It's pretty painless creating serializable classes...

Implementing the Service

The actual Web service has to implement our service interface. Here is an excerpt of our implementation:

public class SFServiceImpl extends RemoteServiceServlet implements SFService {

    // private static Soap port = null;
    private static SoapBindingStub port = null;

    /* .. implementation methods go here .. */
}

This class can be deployed on a server such as Apache Tomcat for example, where it functions as a servlet. This remote service needs to make calls to Salesforce.com to retrieve Account and Contact information. To implement this, we simply used Apache Axis to generate Java classes for the WSDL representing our Salesforce.com account. The login() method is the critical one:

private void login() throws Exception {
    port = (SoapBindingStub) new SforceServiceLocator().getSoap();
    LoginResult loginResult = port.login("YOURUSERNAME", "YOURPASSWORD");
    port._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY,
            loginResult.getServerUrl());
    SessionHeader sh = new SessionHeader();
    sh.setSessionId(loginResult.getSessionId());
    port.setHeader(new SforceServiceLocator().getServiceName()
            .getNamespaceURI(), "SessionHeader", sh);
}

It may look complicated, but I assure you that you only have to write this once. I simply copied it from the Salesforce.com instructions in the Force.com SOAP API Developer's Guide. See the "Quick Start" and "Sample Code Walkthrough" sections. Once you're logged in, it's plain sailing as you'll have a SOAP port binding that you can use to make Force.com calls. As an example, here is an excerpt from the getAccounts() method:

public AccountInfo[] getAccounts() {
    List<AccountInfo> _accounts = null;
    QueryResult qr = null;
    String query = 
      "select Name, description, accountNumber, numberOfEmployees, 
       Id, Industry from Account";
    qr = port.query(query);

Note how the query is a simple Apex query, and we execute it by calling the query() method on the port variable set up during login.

    SObject[] records = qr.getRecords();
   _accounts = new ArrayList<AccountInfo>(records.length);

Queries return a number of instances of SObject. All we need to do now is iterate over the returned objects, extracting the information we want and creating our own array of AccountInfo objects.

   for (SObject sobj : records) {
     Account account = (Account) sobj;
     AccountInfo c = new AccountInfo(account.getName());
     c.setAccountNumber(account.getAccountNumber());       
     c.setAccountDescription(account.getDescription());      
    _accounts.add(c);
   }
   AccountInfo[] cc = new AccountInfo[_accounts.size()];
   return (AccountInfo[]) _accounts.toArray(cc);
}

That's it. The other methods are very similar to this - they simply use different queries. Remember, this code runs on the server! See the appendix for details on how we deployed the service.

Creating the Local Data Layer with Google Gears

Referring back to Figure 3, you'll recall that the Local Data Layer is what our web application should interact with when the application is offline. The sequence of events that our application supports is as follows:

  • Before going offline, the user hits the "Go Offline" button.
  • This makes a call into the Server Data Layer to retrieve all the Contact information for all of our accounts, and it stores them in a local Google Gears database
  • When contact information for a particular account is requested by the user, the Local Data Layer queries the local database and retrieves the Contact information.

So this is the crunch -- this is where Google Gears comes into its own.

Google Gears' Database Module

Google Gears supplies a Database Module API that provides a "browser-local relational data storage to your JavaScript web application." In other words, it provides a relational database that you can call from within your browser. The syntax that the database groks is that of SQLite, and will be familiar to anyone that has written SQL.

To make life easier for ourselves, we've used an additional Google library, the Google API Library for Google Web Toolkit. This library provides an integration between Google Web Toolkit and Google Gears. Installation is simple. Make the library available to your application, and then modify the GWT configuration file to include its functionality:

<inherits name='com.google.gwt.user.User'/>
<!--  jon added -->
<inherits name='com.google.gwt.gears.Gears'/>

From this point on, you can simply interact with the Database class. For example, here's an excerpt from our main class, MyApplication.

Database db = new Database("jons-gears-demo");
db.execute("drop table if exists Contacts");
db.execute(
     "create table if not exists Contacts (accountNumber varchar(255), 
      name varchar(255), phone varchar(255) )");

Your web browser could be pointing to several different web applications enhanced with Google Gears, so typically you name the database something unique. In this case, "jons-gears-demo". After creating the database, we drop any existing tables and recreate the local Contacts table that will store information when we go offline.

As you can see, the Database object provides all the access you need to create tables, drop tables, and as we'll soon see, storing and retrieving data. It's that simple... Just remember, this all executes on the browser.

Storing and Retrieving from the Database

Storing and retrieving from the database is embarrassingly simple. You can treat it just like you would an SQL database. Here's how we store information:

public void goOffLine() {              
  serverDataLayer.getAllContacts(new AsyncCallback() {

     public void onSuccess(Object result) {
       ContactInfo[] ci = (ContactInfo[]) result;
       for (int i=0; i< ci.length; i++) {
          db.execute("insert into Contacts values (?, ?, ?)", 
            new String[] {ci[i].getAccountNumber(), 
                          ci[i].getContactName(), 
                          ci[i].getContactPhone() });
       }
     }

     public void onFailure(Throwable caught) {
              Window.alert("Failed to get all contacts: " + caught);
     });
}

This method starts by making a call to the getAllContacts() method on our Server Data Layer. With each ContactInfo object in the result, it executes the insert into Contacts values (?, ?, ?) SQL, inserting the result into the database.

The method to retrieve the data, which will be called when you click on an Account while offline, is equally straightforward:

// Retrieve account details for given account number
public AccountDetailsInfo getAccountDetails(final String accountNumber) {
    AccountDetailsInfo adi = new AccountDetailsInfo();
    adi.setAccountNumber(accountNumber);
    ResultSet rs = db.execute(
       "select name,phone from Contacts where accountNumber = ?", 
        new String[] {accountNumber});

    for (int i = 0; rs.isValidRow(); ++i, rs.next()) {
        ContactInfo ci = new ContactInfo();
        ci.setContactName(rs.getFieldAsString(0));
        ci.setContactPhone(rs.getFieldAsString(1));
        adi.getContacts().add(ci);
    }
    return adi;
}

We execute an SQL query, select name,phone from Contacts where accountNumber = ? to retrieve the desired data, and construct a return object.

The Data Switch and Entry Point

Now, let's put all this together. Recall that we defined the data switch (see Figure 3) as the functionality that lets us choose between the Server Data Layer and Local Data Layer.

We've implemented this with the method MyApplication.displayAccountDetails(). Recall that it's this method that gets called when a user clicks on a row in the Account List Panel. So let's look at the implementation:

public void displayAccountDetails(final String accountNumber) {
  // if we're online, it's pretty simple - go and grab the details from
  // the Server Data Layer
  if (isOnline()) {
    serverDataLayer.getAccountDetails(accountNumber, new AsyncCallback() {
        public void onFailure(Throwable caught) {
            // ...
        }

        public void onSuccess(Object result) {
            AccountDetailsInfo adi = (AccountDetailsInfo) result;
            adi.setAccountNumber(accountNumber);
            accountDetails.showDetailsForAcccount(adi);
        }
    });
  } else {
    // We're offline - use the Local Data Layer
    AccountDetailsInfo adi = localDataLayer.getAccountDetails(accountNumber);
    accountDetails.showDetailsForAcccount(adi);
  }
}

If we're online, it simply makes a call to retrieve the account details from the server using the RPC mechanism described in the previous section. If we're offline, it instead uses the local data layer. Simple!

All that's left is the main application entry point:

public class MyApplication implements EntryPoint {
    private AccountList accountList = new AccountList();
    private AccountDetails accountDetails = new AccountDetails();
    private MenuBar menuBar;

    void displayAccountDetails(final String accountNumber) {
         ...
    }         

    void onModuleLoad() { 
        serverDataLayer = (SFServiceAsync) GWT.create(SFService.class);
        // ...
        Database db = setupGears();
        localDataLayer = new LocalDataLayer(db, serverDataLayer);  
        // ...
        menuBar = new MenuBar(accountList, serverDataLayer, localDataLayer);
        panel.add(menuBar);
        // ...
        RootPanel.get().add(panel);
    }
 }

When GWT loads an application, it calls the onModuleLoad() method, which in our case creates the various service layers, sets up the database, instantiates the UI elements and adds them to the display panel.

Advanced Synchronization

As it stands, our application requires an explicit signal from the user to indicate that you're about to go offline. This doesn't provide for the best user experience, and your application would be far more useful if it didn't require this. An alternative approach is for your application to periodically synchronize in the background, whenever it's able to establish a connection to the server.

GWT gives you all the tools you need to implement this type of synchronization, though your synchronization logic will become more complex as soon as you extend your application to handle updates, and you end up with several clients updating the same data and need to resolve conflicts!

Implementing synchronization will also require some architectural changes. For example, you can change the way our sample application works by having it always read the contact information from the local service, and having a background thread that populates the local database. The WorkerPool API, part of Google Gears, provides a convenient mechanism for running code in the background to implement this sort of functionality. We leave this an exercise for the reader.

Download

Click to download the source files.

Summary

This tutorial shows that "offline" no longer means what it used to mean. By enhancing your Web applications with Google Gears, they can be taken offline and still function well. In demonstrating this functionality, this tutorial has also shown how to use Google Web Toolkit to build Ajax-enhanced Web applications, and Apache Axis to access Salesforce.com using web services.

References

Appendix

Appendix 1: Creating your first GWT Application

To build this application from scratch, it's worth while looking at the Google Web Toolkit Getting Started Guide. We created our Eclipse and GWT application using the following commands described in the guide:

projectCreator -eclipse MyProject4
applicationCreator -eclipse MyProject4 com.jon.client.MyApplication 

Appendix 2: Creating the Java Web Services Integration

Our application makes calls from the server to Salesforce.com using the Force.com SOAP API. We simply followed the quick start guide. Here's a summary:

  • Log into your Salesforce.com account as an administrator. Click Setup, App Setup and then Integrate. Download the enterprise WSDL.
  • Generate the Java classes by running java -classpath XXX org.apache.axis.wsdl.WSDL2Java -a enterprisewsdl.xml as outlined in the Apache Axis documentation.
  • Compile the resulting Java classes and include them on the server's application deployment.

As a result of following this procedure, you'll have an Account Java object, as used in SFServiceImpl.java as well as other Java classes that can be used to log in to Salesforce.com.

<a name="deploys"></a>

Appendix 3: Deploying the Application

Our Eclipse project is called MyProject4 and the application is called MyApplication. The bin directory contains the compiled Apache Axis output com/sforce/soap/enterprise/* as well as our server com/jon/*.

When you create an application with GWT, it provides a script MyApplication-compile that allows you to compile the application from the command line.

To deploy the application and service, you need to copy the compiled GWT application and server files over to your application server. We're using Apache Tomcat 5.5, so we simply execute the following:

./MyApplication-compile 
cp -R bin/com /Java/apache-tomcat-5.5.20/webapps/ROOT/WEB-INF/classes/
cp -R www/com.jon.MyApplication /Java/apache-tomcat-5.5.20/webapps/ROOT/

The web application has a web.xml configuration file that installs the servlet (which hosts the service). Ours looks like this:

<web-app>
    <display-name>My Service Implementation</display-name>
    <description></description>

    <servlet>
        <servlet-name>SFService</servlet-name>

        <servlet-class>com.jon.server.SFServiceImpl</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SFService</servlet-name>
        <url-pattern>/com.jon.MyApplication/sfservice</url-pattern>

    </servlet-mapping>
</web-app>

As you can see, we map the service to /sfservice. If you look at the GWT configuration file, MyApplication.gwt.xml, you'll see the service defined with the same path:

<servlet path='/sfservice' class='com.jon.server.SFServiceImpl'/>

The only other thing to note is that your application server will need all the appropriate libraries before the application will deploy. Figure 4 has a snapshot of the libraries we used.


Lib.gif
Figure 4: The libraries in WEB-INF/lib on the application server


The gwt-servlet.jar comes with your GWT distribution. All the other libraries are standard libraries that you need to deploy an Apache Axis application.