Skip to main content

Developing MEP Client Applications - Part 1

Posted by ryan_shoemaker on August 20, 2008 at 8:25 AM PDT

In my previous post, we took a closer look at the MCBO API and all of its features. Now it is time to show how to use the APIs to develop an MEP client application.

In Part 1, we will focus on the fundamentals of the API: creating a SyncManager, initiating a sync, and examining the sync results. In part 2, we will study the security features provided in the client API.

MusicDB Sample App

We'll be examining the MCBO API within the context of the "MusicDB" sample app that ships with MEP v1.0. This sample client is able to synchronize basic music album data with a JDBC back-end. For more information about how the MEP connector was developed for this sample app, please follow Santiago's blog about using the ECBO API to develop MEP connectors. The source code for the MusicDB client application is available for download in the MEP client bundle.

Data Model

One of MEP's strongest features is that it is completely agnostic about your data model. As long as you can figure out how to serialize your BusinessObjects, MEP will be able to sync the data with the MEP gateway server. In the case of the MusicDB sample app, we are sending some very basic album data back and forth to the JDBC back-end: album name, artist name, date, and rating. Let's take a look at how to implement a BusinessObject to manage this data model.

Implementing A BusinessObject

There are only a few steps required to implement your business object:

  1. extend com.sun.mep.client.api.BusinessObject
  2. implement getters/setters for your bean properties
  3. implement the serializer and deserializer methods

The class definition and bean properties look like this:

 public class Album extends BusinessObject {

     private static final String DEFAULT_VALUE = "$$default$$";

     String artist;

     Date datePublished;

     int rating;

     ...

Each business object must have a unique name which is stored in the 'name' field of the BusinessObject base-class. Since the business objects are stored on the mobile device's filesystem, there is a restriction that the name must be a unique identifier that can be used as a file name. In order to differentiate between different kinds of business objects, you must also assign a unique extension. For the "Album" business object type, we've choosen ".alb" by implementing the getExtension() method like this:

     public String getExtension() {
         return ".alb";
     }

The combination of the unique name of each instance of Album and the extension will determine the name of the object stored on the mobile device's filesystem.

Implementing the getters and setters for the bean properties is as simple as:

     public String getArtist() {
         return artist;
     }

     public void setArtist(String artist) {
         this.artist = artist;
     }

     public Date getDatePublished() {
         return datePublished;
     }

     public void setDatePublished(Date datePublished) {
         this.datePublished = datePublished;
     }

     public int getRating() {
         return rating;
     }

     public void setRating(int rating) {
         this.rating = rating;
     }

The business object's name field is immutable - once you set the object's name, you can not change it.

All business objects must provide the ability to serialize and deserialized to a byte array. The actual format used for serialization is open ended, but must be part of the contract between the connector and the mobile client application. For music albums, serialization is implemented using DataOutputStream and DataInputStream:

     public byte[] serialize() throws IOException {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         DataOutputStream dOut = new DataOutputStream(out);

         dOut.writeUTF(getName());
         dOut.writeUTF(artist != null ? artist : DEFAULT_VALUE);
         dOut.writeUTF(datePublished != null ?
              getSimplifiedDate(datePublished) : DEFAULT_VALUE);
         dOut.writeUTF(Integer.toString(rating));
         dOut.flush();
       
         return out.toByteArray();
     }

     public void deserialize(byte[] array) throws IOException {
         ByteArrayInputStream in = new ByteArrayInputStream(array);
         DataInputStream dIn = new DataInputStream(in);

         albumName = dIn.readUTF();
         artist = dIn.readUTF();
         if (artist.equals(DEFAULT_VALUE)) {
             artist = null;
         }
       
         DateStringParser dateParser = new Iso8601Converter();
         String date = dIn.readUTF();
         Calendar c = dateParser.stringToCalendar(date, TimeZone.getDefault());
         datePublished = date.equals(DEFAULT_VALUE) ? null : c.getTime();
       
         rating = Integer.parseInt(dIn.readUTF());
     }

You can see that there is a special token 'DEFAULT_VALUE' which is used to determine when Album fields are not set. There is also some helper code for parsing the date format.

Reading and Writing Business Objects

In order to store and retrieve instances of your business objects on the mobile device, you must use the BusinessObjectStorage class. It provides some high-level APIs to read, write, enumerate, and delete business object instances.

 public class BusinessObjectStorage {
     public Vector listBusinessObjectNames() {...}
     public void readBusinessObject(BusinessObject result) {...}
     public void writeBusinessObject(BusinessObject obj) {...}
     public void deleteBusinessObject(BusinessObject obj) {...}
     public void deleteBusinessObject(String name) {...}
     public boolean deleteAllBusinessObjects() {...}
}

In the MusicDB sample, business objects are read like this:

     // Get the name of the selected album from the UI and read the BO
     selectedAlbum = getAlbumsForm().getString(getAlbumsForm().getSelectedIndex());
     Album a = new Album(selectedAlbum.substring(0, selectedAlbum.length()-4));
     boStorage.readBusinessObject(a);

and written like this:

     // Get album details from UI, create a new Album, and store it 
     DateStringParser dateParser = new Iso8601Converter();
           
     Album a = new Album();
     a.setArtist(getArtistField().getString());
     a.setName(getNameField().getString());
     final String dateString = getDateField().getString();
     if(dateString != null && dateString.length() > 0) {
         Calendar c = dateParser.stringToCalendar(dateString,TimeZone.getDefault());
         // calendar details omitted
         a.setDatePublished(c != null ? c.getTime() : null);
     }
     a.setRating(Integer.parseInt(getRatingField().getString()));

     boStorage.writeBusinessObject(a);

SyncManager

SyncManager is responsible for mangining the synchronization of a particular class of business objects with the MEP gateway server. There is a 1:1 relationship between instances of SyncManager and classes of business objects. In the case of MusicDB, we only have one kind of business object - Album (*.alb). To create the SyncManager, you simply construct it and specify the business object extension. SyncManager also serves as a factory for BusinessObjectStorage and it also manages the MEP gateway server address and sync credentials:

     SyncManager syncMgr = new SyncManager(".alb");
     BusinessObjectStorage boStorage = syncMgr.getBusinessObjectStorage();
     ...
     // get the url, user, & pass from the UI and store them
     syncMgr.setCredentials(
         getUserName().getString(),
         getPassword().getString(),
         getSyncServer().getString());

Performing a Synchronization

When you're ready to perform a synchronization, all you have to do is call the sync method on SyncManager and tell it which kind of synchronization to perform. The different kinds of sync types are available in an enum called SyncType. Initiating a fast sync would look like this:

     syncMgr.sync(SyncType.FAST_SYNC);

Looking at the SyncResults

Assuming the call to sync returns without throwing an exception, you can now examine the sync results which are contained in an instance of SyncResults and draw them on the UI:

     SyncResults results = syncMgr.getSyncResults();
     Form f = getResultsForm();
     f.deleteAll();
     f.append(new StringItem("ElapsedTime: ", getElapsedTime()));
     f.append(new StringItem(null, null));
     f.append(new StringItem("CLIENT -> SERVER:", null));
     f.append(new StringItem("Added   :", String.valueOf(results.getClientAdditions())));
     f.append(new StringItem("Updated :", String.valueOf(results.getClientUpdates())));
     f.append(new StringItem("Deleted :", String.valueOf(results.getClientDeletions())));
     f.append(new StringItem("SERVER -> CLIENT", null));
     f.append(new StringItem("Added   :", String.valueOf(results.getServerAdditions())));
     f.append(new StringItem("Updated :", String.valueOf(results.getServerUpdates())));
     f.append(new StringItem("Deleted :", String.valueOf(results.getServerDeletions())));

That wraps up all of the basic functionality provided in the MCBO API. In the next entry, we will take a closer look at all of the security features available in the API. Stay tuned...

Related Topics >>