Thursday, June 3, 2010

Stubbing Out The Contacts Sample Application

So we generated the starter GWT and AppEngine application. Now it is time to clean this one up so we are only left with stuff that we need. We will start first with deleting some project starter classes which we do not need, and then we will edit some of the classes and html files that we will keep.






Remove Unwanted Files

Under src directory delete the files:
  • GreetingService.java
  • GreetingServiceAsync.java
  • GreetingServiceImpl.java
  • FieldVerifier.java

Edit Files

After you have deleted these you will see compile errors in SampleContactsApp.java since it is missing lots of references now. Let's go ahead and edit this file to clean out all the old starter code:
package gaej.example.contacts.client;

import com.google.gwt.core.client.EntryPoint;

/**
 * Entry point classes define onModuleLoad().
 */
public class SampleContactsApp implements EntryPoint {

 @Override
 public void onModuleLoad() {
  // TODO Auto-generated method stub
  
 }
 
}

Ok, now the compile errors are gone, but we are not done yet. We also need to clean up SampleContactsApp.html since it is now referencing widgets that no longer exist (nameFieldContainer and sendButtonContainer). Let's clean up this file too. Open it up and edit it so that the body looks like follows:

<body>

    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
    
    <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>


  </body> 


Ok that should do it. Our starter project is now ready for further stubbing.

Start Stubbing

Since we are building a contacts application, it makes sense that we create a something which will hold all essential contact information. Since we like to use interfaces, we shall first create an interface called Contact:

package gaej.example.contacts.shared;

import java.io.Serializable;

public interface Contact extends Serializable {

 public String getId();
 public String getFirstName();
 public String getLastName();
 public String getEmail();
 public void setId(String id);
 public void setFirstName(String firstName);
 public void setLastName(String lastName);
 public String getFullName();
 public void setEmail(String email);

}

So what we have here is a Contact interface which will provide a contact's firstName, lastName, and email. That is everything that we need so far, but it may grow of course (maybe with an address and a telephone number). Over time this object can become very large having a lot of data fields.

Now remember that for our first screen we just want a list of contact names with a check box next to it. In essence, for the first screen, we do not need many of the data fields contained here. Most likely in many other situations it will be the same: we will only need the fields id, firstName, and lastName.

One of the best practices for GWT is to transfer as little data as necessary between the server and the client, so it makes sense that we create a light-weight version of our Contact interface. Let's call it ContactLight:
package gaej.example.contacts.shared;

import java.io.Serializable;

public interface ContactLight extends Serializable {

 public String getId();
 public String getDisplayName();
 public void setId(String id);
 public void setDisplayName(String displayName);

}

These contacts need to come from somewhere, so we also need some form of a data storage where we can save and retrieve contacts. For now we will just create a contacts data access interface. Let's call it ContactsDAO:
package gaej.example.contacts.server;

import gaej.example.contacts.shared.Contact;
import gaej.example.contacts.shared.ContactLight;

import java.util.List;

public interface ContactsDAO {

 public Contact getContact(String id);
 public Contact addContact(Contact contact);
 public Contact updateContact(Contact contact);
 public Boolean deleteContact(String id);
 public List<Contact> getContactList();
 public List<ContactLight> getContactLightList();
 public boolean exists(Contact contact);

}

This interface is nothing special really. All we want is a facility that will create, retrieve, update, and delete a contact, as well as retrieve a list of all contacts in one go. Besides retrieving a list of all contacts, it also makes sense to be able to retrieve a list of all contacts in light weight form. Lastly, it also helps to have a function to check whether a contact already exists or not. This can help determine if we are dealing with a new contact (that requires an insert) or an existing object (that requires an update).

Note that the client will never make use of this interface. It will only be used by the server side. As such, we include it in the server package.

Ok, so now we got something where we can create, retrieve, update, and delete our contacts. Now we need a service to pass contacts back and forth between the client and the server. Let's call this service the ContactsService:
package gaej.example.contacts.client;

import java.util.List;

import gaej.example.contacts.shared.Contact;
import gaej.example.contacts.shared.ContactLight;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("contacts")
public interface ContactsService extends RemoteService {
 Contact addContact(Contact contact);
 Contact getContact(String id);
 List<ContactLight> getContactLightList();
 Contact updateContact(Contact contact);
 Boolean deleteContact(String id);
 
}

What we have here is a service that will use GWT-RPC to transfer the Contact and ContactLight objects. Notice the @RemoteServiceRelativePath annotation. This defines the servlet name which the client will use to retrieve our contact objects. To make things complete, we will eventually need to edit the web.xml to reflect our new service. Before we can do so, however, we will need a concrete implementation of this service first, and an asynchronous interface that corresponds with this new interface.

You may have noticed Eclipse complaining that you need to create one after saving the ContactsService interface. This is because GWT-RPC uses an asynchronous communication method. So any GWT-RPC service you create will always require an asynchronous counterpart.

Alright, let's create the asynchronous ContactsService interface. You can do so by either typing it yourself, or by using the QuickFix function of eclipse. In the end, you should get something like this:
package gaej.example.contacts.client;

import gaej.example.contacts.shared.Contact;
import gaej.example.contacts.shared.ContactLight;

import java.util.List;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface ContactsServiceAsync {

 void addContact(Contact contact, AsyncCallback<Contact> callback);
 void deleteContact(String id, AsyncCallback<Boolean> callback);
 void getContact(String id, AsyncCallback<Contact> callback);
 void getContactLightList(AsyncCallback<List<ContactLight>> callback);
 void updateContact(Contact contact, AsyncCallback<Contact> callback);

}

You may notice that it is almost identical to the ContactsService interface created earlier, except that now the return types are changed into method parameters of type AsyncCallback. You will use these parameters to pass in your callback function, but more on that later. Now its time to create the concrete implementation for ContactsService. Let's call this ContactsServiceImpl:
package gaej.example.contacts.server;

import java.util.List;

import gaej.example.contacts.client.ContactsService;
import gaej.example.contacts.shared.Contact;
import gaej.example.contacts.shared.ContactLight;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

@SuppressWarnings("serial")
public class ContactsServiceImpl extends RemoteServiceServlet implements
  ContactsService {

 @Override
 public Contact addContact(Contact contact) {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public Boolean deleteContact(String id) {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public Contact getContact(String id) {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public List<ContactLight> getContactLightList() {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public Contact updateContact(Contact contact) {
  // TODO Auto-generated method stub
  return null;
 }

}

Notice that the interfaces ContactsService and ContactsServiceAsync were created in the package client, while the concrete implementation ContactsServiceImpl is created in the server package. After all, only the server will host the service.

Alright, now that we have the concrete implementation of the contacts service, we can start to edit the web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  
  <!-- Servlets -->
  <servlet>
    <servlet-name>contactsServlet</servlet-name>
    <servlet-class>gaej.example.contacts.server.ContactsServiceImpl</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>contactsServlet</servlet-name>
    <url-pattern>/samplecontactsapp/contacts</url-pattern>
  </servlet-mapping>
  
  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>SampleContactsApp.html</welcome-file>
  </welcome-file-list>

</web-app>

Having a service that just returns null objects is not much use of course, even during development. So let's create a concrete implementations of our ContactsDAO, Contact, and ContactLight interfaces.

First up, the concrete Contact implementation, which we shall call ContactImpl:
package gaej.example.contacts.shared;

@SuppressWarnings("serial")
public class ContactImpl implements Contact {

 private String id;
 private String firstName;
 private String lastName;
 private String email;
 
 public ContactImpl() {}
 public ContactImpl(String id, String firstName, String lastName, String email) {
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
  this.email = email;
 }
 
 @Override
 public String getEmail() {
  return email;
 }

 @Override
 public String getFirstName() {
  return firstName;
 }

 @Override
 public String getFullName() {
  return firstName + " " + lastName;
 }

 @Override
 public String getId() {
  return id;
 }

 @Override
 public String getLastName() {
  return lastName;
 }

 @Override
 public void setEmail(String email) {
  this.email = email;
 }

 @Override
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 @Override
 public void setId(String id) {
  this.id = id;
 }

 @Override
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

}

Now the corresponding ContactLightImpl:
package gaej.example.contacts.shared;

@SuppressWarnings("serial")
public class ContactLightImpl implements ContactLight {

 private String id;
 private String displayName;
 
 public ContactLightImpl() {}
 public ContactLightImpl(String id, String displayName) {
  this.id = id;
  this.displayName = displayName;
 }
 
 @Override
 public String getDisplayName() {
  return displayName;
 }

 @Override
 public String getId() {
  return id;
 }

 @Override
 public void setDisplayName(String displayName) {
  this.displayName = displayName;
 }

 @Override
 public void setId(String id) {
  this.id = id;
 }

}

Not very complicated right? Indeed, but there is something very important here. Since these objects are serializable, it is very important to have a public constructor that takes no parameters! The default constructor is by default protected, so it is important that you declare a public one yourself. If you look at the above concrete implementations, you will see that both have a public constructor which does not take any arguments.

For development we will just be using some mock data, so we do not need to create a full-fledged DAO that interacts with storage or whatever. All we need is some test data. So for this purpose let's create a mock contacts DAO called ContactsDAOMock:
package gaej.example.contacts.server;

import gaej.example.contacts.shared.Contact;
import gaej.example.contacts.shared.ContactLight;
import gaej.example.contacts.shared.ContactImpl;
import gaej.example.contacts.shared.ContactLightImpl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

public class ContactsDAOMock implements ContactsDAO {

 private static final String[] contactsFirstNameData = new String[] {
   "Hollie", "Emerson", "Healy", "Brigitte", "Elba", "Claudio",
   "Dena", "Christina", "Gail", "Orville", "Rae", "Mildred",
   "Candice", "Louise", "Emilio", "Geneva", "Heriberto", "Bulrush",
   "Abigail", "Chad", "Terry", "Bell" };

 private final String[] contactsLastNameData = new String[] { "Voss",
   "Milton", "Colette", "Cobb", "Lockhart", "Engle", "Pacheco",
   "Blake", "Horton", "Daniel", "Childers", "Starnes", "Carson",
   "Kelchner", "Hutchinson", "Underwood", "Rush", "Bouchard", "Louis",
   "Andrews", "English", "Snedden" };

 private final String[] contactsEmailData = new String[] {
   "mark@example.com", "hollie@example.com", "boticario@example.com",
   "emerson@example.com", "healy@example.com", "brigitte@example.com",
   "elba@example.com", "claudio@example.com", "dena@example.com",
   "brasilsp@example.com", "parker@example.com",
   "derbvktqsr@example.com", "qetlyxxogg@example.com",
   "antenas_sul@example.com", "cblake@example.com",
   "gailh@example.com", "orville@example.com",
   "post_master@example.com", "rchilders@example.com",
   "buster@example.com", "user31065@example.com",
   "ftsgeolbx@example.com" };

 private final HashMap<String, Contact> contacts = new HashMap<String, Contact>();

 public ContactsDAOMock() {
  initContacts();
 }

 private void initContacts() {
  for (int i = 0; i < contactsFirstNameData.length
    && i < contactsLastNameData.length
    && i < contactsEmailData.length; ++i) {
   Contact contact = new ContactImpl(String.valueOf(i),
     contactsFirstNameData[i], contactsLastNameData[i],
     contactsEmailData[i]);
   contacts.put(contact.getId(), contact);
  }
 }

 @Override
 public Contact addContact(Contact contact) {
  contact.setId(String.valueOf(contacts.size()));
  contacts.put(contact.getId(), contact);
  return contact;
 }

 @Override
 public Boolean deleteContact(String id) {
  contacts.remove(id);
  return true;
 }

 @Override
 public boolean exists(Contact contact) {
  return contacts.containsKey(contact.getId());
 }

 @Override
 public Contact getContact(String id) {
  return contacts.get(id);
 }

 @Override
 public List<ContactLight> getContactLightList() {
  ArrayList<ContactLight> light = new ArrayList<ContactLight>();
  for (Contact contact : contacts.values()) {
   light.add(new ContactLightImpl(contact.getId(), contact
     .getFullName()));
  }
  return Collections.unmodifiableList(light);
 }

 @Override
 public List<Contact> getContactList() {
  return Collections.unmodifiableList(new ArrayList<Contact>(contacts
    .values()));
 }

 @Override
 public Contact updateContact(Contact contact) {
  contacts.remove(contact.getId());
  contacts.put(contact.getId(), contact);
  return contact;
 }

}


This mock is more or less a copy of the one in the google documentation. You can change the data if you want.

Lastly, we will need to revisit ContactsServiceImpl.java to have it make use of our new DAO implementation:
package gaej.example.contacts.server;

import java.util.List;

import gaej.example.contacts.client.ContactsService;
import gaej.example.contacts.shared.Contact;
import gaej.example.contacts.shared.ContactLight;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

@SuppressWarnings("serial")
public class ContactsServiceImpl extends RemoteServiceServlet implements
  ContactsService {

 private ContactsDAO contactsDAO = new ContactsDAOMock();
 
 @Override
 public Contact addContact(Contact contact) {
  return contactsDAO.addContact(contact);
 }

 @Override
 public Boolean deleteContact(String id) {
  return contactsDAO.deleteContact(id);
 }

 @Override
 public Contact getContact(String id) {
  return contactsDAO.getContact(id);
 }

 @Override
 public List<ContactLight> getContactLightList() {
  return new ArrayList<ContactLight>(contactDAO.getContactLightList());
 }

 @Override
 public Contact updateContact(Contact contact) {
 if ( contactsDAO.exists(contact) ) {
  return contactsDAO.updateContact(contact);
 } else {
  return contactsDAO.addContact(contact);
 }
 }

}


Alright, we now have stubbed the service part of our application, and even created some concrete implementations to help us in with development. Next up, creating the MVP framework for our app.

No comments:

Post a Comment