Rebecca Searls' Blog

pageicon Wednesday Feb 25, 2009

Mobilizing a Web Service Using JAX-RS connector in Sun GlassFish Mobility Platform 1.1 - Part III

Part II examined the JAX-RS connector interface.  This segment describes some of the design challenges and explores some of the solutions that were implemented in this connector.

First, let's look at the data that is going to be transmitted.  If you examine your Salesforce Developer account, you'll notice a few predefined customer accounts.  Although there are a variety of subcategories for each account, you are only interested in the Contacts and Tasks categories and the basic account information.

The following design decisions determined how the connector was implemented:

  • Selecting the data that the client will present
  • Deciding how to implement the BusinessObjects
  • Identifying the entity that changed the data of a BusinessObject since the last sync session

What Data Will the Client Present?

The first design decision was selecting the data that the client would present.  Because this was a demonstration project, we wanted to keep the data set small and representative.  For an account, we chose fields from the Account category: account  name and number, company address, and some sales information.  Contact information included just name, title, email, and phone number.  Title, description, dates, and status information were selected for Tasks.

How Will the BusinessObjects Be Implemented?

The second design decision was to decide what the BusinessObject [1]  was going to look like.  Should we transmit all of an account's contacts, tasks, and account information as a single BusinessObject or should there be a separate BusinessObject for each data type?  Each option had advantages and disadvantages.

The first option was desirable because you could define a single BusinessObject to hold all the data for a single account.  There would be a relatively small number of files for the client and connector to process, making the design similar to that of Personal Information Management (PIM) software.  There were several disadvantages to this design. Only with greate difficulty could the connector detect newly created tasks and tasks tagged for deletion by the client.  Every change to the data, either from the client or the repository, required the connector to evaluate all of the contents of the BusinessObject during sync cycles, resulting in very high processing overhead.

The second option, creating separate BusinessObjects for each data type, resolved the problem of detecting data changes and lowered the processing overhead, but it added complexity to both client and connector implementations because many more classes were required.  This design presented a new issue as well.  We needed a consistent and unique identifier for each data type for as long as the data existed in the repository.  We also needed a means to convey the relationship of the objects to each other without having to open and read every BusinessObject.  This was solved with a file naming algorithm.  We discovered that we could uniquely identify each data type by using the database's own table id.  This value is unique and exists as long as the data does.  Using the id, we created the following file naming format that conveyed the data associations:

  • Account ECBO files were named <id>
  • Associated contact ECBOs were named <id>_C_<id>
  • Task ECBOs were named <id>_T_<id>

When the client creates a new task, the file is named <id>_T_<x>, where x is an integer starting with 0. Each new task is given an incremental number.  This allows the connector to easily identify tasks to be created in the repository.  Deleted and updated BusinessObjects are handled by the normal sync mechanisms in the connector, that is, deleteBusinessObject() and putBusinessObject() are called.  This format makes it quite easy to parse the filenames and process the data appropriately.

    This pseudocode shows how the client application parses and processes the files.

Hashtable masterTable ...
    List fList = get list of files on device.
    for (File f : fList){
        String fName = f.getName();
        if (fName.length() > 24){
          int loc = fName.indexOf("_T_");
          if ( loc > -1){
            String tmpN = fName.substring(0,loc);
            SFAccount sfa = masterTable.get(tmpN);
            if (sfa == null){
                //- parent object does not exist yet.  Create it.  The parent
                //- will be found later.
                sfa = new SFAccount();
                masterTable.put(name, sfa);
            }
            sfa.addTaskFilename(fName); //- Just put in list; process array
                                        //- contents on 1st user selection of account
          }
           //- same as above for Contact
        } else {
            //- an account
            String name = fName.substring(0, fName.length -4);
            SFAccount sfa = masterTable.get(name);
            if (sfa == null){
                sfa = new SFAccount();
                masterTable.put(name, sfa);
            }
            //- If sfa was not null, a Contact or Task child created it because it was
            //- earlier in the list.  Fill it in now.
            sfa.deserialize(f);
        }
    }



One disadvantage to this format is that each id string is 18 characters long, making the filenames longer than we would have liked.  This disadvantage was easily offset by the advantages.

Three BusinessObject classes, SFAccount, SFContact and SFTask, were created for the connector.  SFAccount implements the required BusinessObject methods serialize() and deserialize(), that write and read the data passed between the connector and client.  This class only generates business objects that contain the account information.  The generation of Contact and Task business objects is performed by SFContact and SFTask, respectively.

The connector makes a call to the Salesforce web service requesting account, contact, and task records.  Here is the query statement.

SELECT  Id,AccountNumber,Name,
                    BillingCity,BillingPostalCode,BillingState,BillingStreet,
                   Website,Phone,Fax,NumberofLocations__c,NumberOfEmployees,Type,
                   Industry,AnnualRevenue,SLA__c,SLAExpirationDate__c,SLASerialNumber__c,
          (SELECT  Id,AccountId,FirstName,LastName,Title,Email,MobilePhone,Phone,LastModifiedDate FROM Account.Contacts),
          (SELECT  Id,AccountId,Subject,Description,ActivityDate,Priority,Status  FROM Account.Tasks)
    FROM Account



The connector then creates a new SFAccount object for each returned account and calls method extractData() to process the record.  Method extractData() extracts the account information and writes it to the local fields.  It creates and calls SFContact and SFTask objects, which extract their information from the record.  SFAccount keeps a list of the SFContact and SFTask objects for later processing.


public byte[] serialize() throws IOException {
        //- Serialize the local fields of basic account information.
        //- DO NOT serialize the Contract or Task objects here.  Each class
        //- serialized itself when called.
    }

    public void deserialize(byte[] array) throws IOException {
        //- Deserialize basic account information only
        //- into the local fields.
    }


    public void extractData(Account a) {
        //- pseudocode for processing data of a single account requested
        //- by a query to the backend repository
        Extract account data; write into local fields
        setName(a.getId());  //- set filename
        foreach Contact in list of contacts{
            new SFContact
            call SFContact to extract data
            add SFContact to contactList
        }
        foreach Task in list of tasks{
            new SFTask
            call SFTask to extract data
            add SFTask to taskList
        }
    }


    public List<SFContact> getSFContactList(){
        return contactList;
    }

    public List<SFTask> getSFTaskList(){
        return taskList;
    }



BusinessObjects SFContact and SFTask have implementations of methods serialize(), deserialize(), and extractData().  They also have a method getSObject(), which creates a Salesforce web service object that contains the values of the local fields of the object.  The SObjects are used when data in the backend repository is created or updated.


SFContact
    public byte[] serialize() throws IOException {}
    public void deserialize(byte[] array) throws IOException {}
    public void extractData(Contact c){
        //- Extract the record data; write data to local fields.
        setName(c.getAccountId() + "_C_" + c.getId());  //- set filename
    }
    public SObject getSObject(){
        //- put local values in a Salesforce contract class
    }

    SFTask
    public byte[] serialize() throws IOException {}
    public void deserialize(byte[] array) throws IOException {}
    public void extractData(Task t){
        //- Extract the record data; write data to local fields.
        setName(t.getAccountId() + "_T_" + t.getId());  //- set filename
    }
    public SObject getSObject(){
        //- put local values in a Salesforce task class
    }



Which Entity Changed the Data of the BusinessObject Since the Last Sync?

The third design decision involved identifying the entity that has changed the data of a BusinessObject since the last sync session.  This identification determines which data takes precedence.  Three cases need to be handled:

  • The client changed the data since the last sync session, so the repository needs to be updated.
  • The repository changed the data and the client needs to be updated.
  • Both the client and repository changed the data, in which case the client is updated with the repository data.


For each data type, account, contract, and task the repository retains a last-altered timestamp.  This timestamp is a piece of data sent in the BusinessObject.  The client application treats it as a read-only field.  The connector uses it to determine if the repository data was altered since the last sync. When the timestamps differ, the repository data takes precedence.  Any changes made to the data by the client are ignored.  If the timestamps for the client and the repository objects are the same, the repository data has not changed, but the client data may have changed.  When this occurs, the connector compares the data sets and updates the repository with client data if differences are found.  Here is the algorithm.


SFTask masterT  //- Data from the repository
    SFTask t        //- Data from the client

    //- Check for change to the repository timestamp
    if (masterT.getLastModifiedDate().equals(t.getLastModifiedDate())) {
        //- check if the client data changed.
        //- Method getDataAsString() returns a simple string concatenation of all the data
        //- minus the id and timestamp.
        if (masterT.getDataAsString().hashCode() != t.getDataAsString().hashCode()) {
            // The client changed not the master; put in the update list
            upDateList.add(t.getSObject());
        }
    }



Lastly, we encountered performance issues while connecting to the the Salesforce web service.  It takes a relatively long time for the Salesforce web server to establish a connection.  This became a performance concern when we connected to the repository for each call to the JAX-RS interfaces. Fortunately, the web service connection remains open for a long time as well, so we implemented a connection pool.  The (user+password, connection) object pair were cached in a hashtable, then, rather than requesting a new connection on each call, we performed a lookup and only created a new connection when one was needed. This improved performance.



Part IV discusses using the URLs for debugging the connector.

-----
[1] Developing MEP Connectors - Part 1
     http://weblogs.java.net/blog/spericas/archive/2008/07/developing_mep.html

    Developing MEP Connectors - Part II
     http://weblogs.java.net/blog/spericas/archive/2008/08/developing_mep_1.html


Comments:

Post a Comment:
  • HTML Syntax: NOT allowed

« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today

Feeds

Search this blog

Links

Weblog menu

Today's referrers

Today's Page Hits: 12