An image of some Dominos

A New RESTful Service

Print Friendly and PDF

Posted: September 7, 2011 | | Categories: IBM Lotus Domino

I have published several articles on this site that illustrated different types of web services for Domino that I have used in some mobile applications. Domino easily supports XML-based web services and platforms such as BlackBerry and Windows Mobile have inherent capabilities that allow applications to consume XML-based web services. Other platforms on the other hand don't have built-in support for XML-based web services, so I've had to implement RESTful services for applications built for other platforms such as Android and iOS. More recently I've decided to make a change to the RESTful agent I've been using for my applications and thought I'd document the changes here.

With my original web services, the idea was that a mobile application would send a request for a list of contacts that matched a search string then, when an application user selected a user, the application would make another call to the server to obtain the details for the selected contact. This process is illustrated in Figure 1.

Figure 1 – Two-part Request Process
Figure 1 – Two-part Request Process

When I first started doing mobile development, performance was important. The networks were slow and mobile devices had limited processing power, memory and battery life. In order to make the most efficient use of the network and increase performance and battery life for the device, I built the services to make the best use of network and processing power. What this meant was that the services would be designed to send a little data as possible over the wireless network and to ensure that data that would possibly not be used by the mobile application wouldn't be sent across the wireless network.

So, in my design, I deliberately built the services so the first request simply retrieved a list of contacts that matched the search string then allowed the application to get detailed contact information only after they determined which contact they were interested in.

It seemed like a good approach and has served me rather well. The problem with this approach though is that it makes building the mobile application more difficult. There are two calls to the server and the results have to be parsed twice as well. If the mobile user selects one contact, then decides it's the wrong one and switches to another, there's another trip to the server for more data.

It's efficient on the network, but inefficient within the application. The thing is, mobile users and mobile device manufacturers stopped caring about network usage and battery life a long time ago. Users want whatever they want and they want it now.

Apple's Dashcode IDE has some pretty cool features that make it easy to work with remote data sets. When you build a Browser project using one of Dashcode's wizards, the application expects a list of records and the details behind the list to all be delivered to the application at one time. The result is only one trip to the server, and a better user experience. There's only one trip forth and back from the server which makes the user feel better, but we're delivering data to the mobile device application that the user may never look at. Oh well.

So, I decided to rewrite my Domino Directory lookup agent so all of the relevant data could be retrieved with a single call to the RESTful agent as shown in Figure 2.

Figure 2 – Single Part Request Process
Figure 2 – Single Part Request Process

With this new service, a single call is made to the server using the following URL:

https://server/database.nsf/contactlookuprest?openagent&ss=SEARCH_STRING

where SEARCH_STRING represents the first three characters (or so) of the contact name the user will be searching for. As an example, a call to the server using the following URL:

https://server/database.nsf/contactlookuprest?openagent&ss=war

would return the following JSON data (or something like it):

[{ "FullName":"Anna Wargo", "LastName":"Wargo", "FirstName":"Anna", "EmailAddress":"awargo@somecompany.com", "OfficePhone":"330.665.1234", "MobilePhone":"330.999.1234"},   {"FullName":"John Wargo", "LastName":"Wargo", "FirstName":"John", "EmailAddress":"john@johnwargo.com", "OfficePhone":"330.123.4567", "MobilePhone":"330.987.6543"}]

The JSON text returned from the server in this case is an array of JavaScript objects. If you expand the JSON text to show the structure of the data, the data will look like the following:

[  
 {  
  "FullName":"Anna Wargo",  
  "LastName":"Wargo",  
  "FirstName":"Anna",  
  "EmailAddress":"awargo@somecompany.com",  
  "OfficePhone":"330.665.1234",  
  "MobilePhone":"330.999.1234"  
 },  
 {  
  "FullName":"John Wargo",  
  "LastName":"Wargo",  
  "FirstName":"John",  
  "EmailAddress":"john@johnwargo.com",  
  "OfficePhone":"330.123.4567",  
  "MobilePhone":"330.987.6543"  
 }  
]

I can use this data in my application by only pulling out the list of FullName values when presenting a list of contacts, then digging in and displaying the contact details only after a contact has been selected within the application.

The brackets (the [ and ] characters) are used to define an array and the curly braces ( the '{' and '}' characters) are used to define a JavaScript object. So, what you're seeing is a essentially a two element array, where each element is a complete JavaScript object describing contact details for a user defined in the Domino Directory database.

To obtain this output, all you need is a simple agent in a Domino Directory database. To create the RESTful service, create a new agent in your Domino Directory and set the agent properties shown in Figure 3. The agent is a web agent; it is triggered by a URL request triggered by the mobile application (or web browser). It uses an 'On Event' trigger and the event is defined as 'Agent list selection.'

Figure 3 – Domino RESTful Agent Properties
Figure 3 – Domino RESTful Agent Properties

Most of the agent's work is done in the Initialize subroutine which performs the following steps:

1. Parse the incoming URL to determine the search string being passed in by the calling program.
2. Search the ($VIMPeopleByLastName) view to locate all users defined in the database whose last name begins with the characters provided in the search string.
3. Loop through the search results and create the JSON array described previously.
4. Print the JSON array text to the console which causes it to be returned to the program that triggered the URL.

In the code, the GetCmdLineValue function is used to parse the HTTP QUERY_STRING variable to retrieve values passed on the URL.

Here is the complete code for the agent:

%REM  
Agent ContactLookupREST  
Created Jan 13, 2010 by John M. Wargo  
Description: Modified the existing DomDirLookupREST agent  
so it returns the complete contact array rather than  
forcing a developer to create a second call to the agent  
after the user selects a contact. This would send more  
data over the network, but will make a mobile application  
that calls it more responsive.  
%END REM  
  
Option Public  
Option Declare  
Option Base 1  
  
'Notes Objects  
Dim db As NotesDatabase  
  
Sub Initialize()  
  
'Print out this line to force Domino to  
'not write it's own HTML gunk at the  
'beginning of the resulting page.  
Print "Content-Type:text/html"  
  
'some constants we'll need  
Const amp = |&|     
Const BR = |<br />|  
Const comma = |,|  
Const errorStr = |"ERROR: |  
Const jsonArrayStart = "\["  
Const jsonArrayEnd = "\]"  
Const jsonObjectStart = |{|  
Const jsonObjectEnd = |}|  
Const quoteStr = |"|  
  
'Notes Objects  
Dim doc As NotesDocument  
Dim s As NotesSession  
Dim tmpName As NotesName  
Dim userView As NotesView  
Dim userDoc As NotesDocument  
Dim ve As NotesViewEntry  
Dim vec As NotesViewEntryCollection  
  
'Other variables  
Dim i As Integer  
Dim jsonText As String  
Dim numContacts As Integer  
Dim queryStr As String  
Dim searchStr As String  
  
'Initialize our Notes session object  
Set s = New NotesSession  
'Then get a handle to the current database  
Set db = s.CurrentDatabase  
'Get a handle to the agent's context  
'(header variables and so on)  
Set doc = s.DocumentContext  
  
'Proper command line for the agent is:  
'https://servername/dbname/ContactLookupREST?openagent&ss=SEARCH_STRING  
queryStr = LCase(doc.Query_String_Decoded(0)) & amp  
searchStr = GetCmdLineValue(queryStr, "&ss=", amp)  
  
'Open the view we're going to lookup against  
Set userView = db.GetView("($VIMPeopleByLastName)")  
If Not userView Is Nothing Then  
'Do we have a search string?  
If Len(Trim(searchStr)) > 0 Then  
  'See if we can find any users by the search string  
  Set vec = userView.GetAllEntriesByKey(searchStr)  
Else  
  'Otherwise get all documents  
  Set vec = userView.AllEntries  
End If  
'Now, if we have any entries - put them into the array  
If vec.Count > 0 Then  
  'Get the number of contacts to use throughout the  
  ' rest of the code  
  numContacts = vec.Count  
  'Start the Array JSON text  
  jsonText = jsonArrayStart  
  'Process all of the entries in the View Entry Collection  
  For i = 1 To numContacts  
    Set ve = vec.GetNthEntry(i)  
    If Not ve Is Nothing Then  
      Set userDoc = ve.Document  
      If Not userDoc Is Nothing Then  
        'Populate the result fields  
        Set tmpName = New NotesName(userDoc.FullName(0))  
        'Start the JSON text we'll be returning  
        jsonText = jsonText & jsonObjectStart & _  
        |"FullName":| & quoteStr & _  
        tmpName.Abbreviated & _  
        quoteStr & comma & _  
        |"LastName":| &  quoteStr & _  
        userDoc.LastName(0) & _  
        quoteStr & comma & _  
        |"FirstName":| &  quoteStr & _  
        userDoc.FirstName(0) & _  
        quoteStr & comma & _  
        |"EmailAddress":| &  quoteStr & _  
        userDoc.InternetAddress(0) & _  
        quoteStr & comma & _  
        |"OfficePhone":| & quoteStr & _  
        userDoc.OfficePhoneNumber(0) & _  
        quoteStr & comma & _  
        |"MobilePhone":| &  quoteStr & _  
        userDoc.CellPhoneNumber(0) & quoteStr & _  
        jsonObjectEnd  
      Else  
            'Unable to get the document  
        jsonText = jsonText & errorStr & _  
        |Unable to open the contact document"|  
      End If  
    Else  
      'Unable to access the View Entry  
      jsonText = jsonText & errorStr & |Unable to access the ViewEntry"|  
    End If  
    'Add a comma if we need one (when there's  
    ' more than one name being returned)  
    If (i < numContacts) Then  
      jsonText = jsonText & comma  
    End If  
  Next i  
'Write our resulting JSON results to the browser  
  Print jsonText & jsonArrayEnd  
  Else  
    'We got nothing, so return an empty array to the  
    'calling program  
    Print jsonArrayStart & jsonArrayEnd  
  End If  
End If  
  
End Sub  
  
%REM  
Function GetCmdLineValue  
Description: Parses a string and returns  
the value between two delimeters  
%END REM  
Function GetCmdLineValue( textStr As String, delim1 As String, delim2 As String) As String  
  
Dim startPos As Integer  
Dim tmpInt As Integer  
Dim valLen As Integer  
  
'find the first ocurrance of the delimeter  
tmpInt = InStr( textStr, delim1)  
'Only continue if we've found something  
If (tmpInt > 0) Then  
  'Figure out where the value starts  
  startPos =  tmpInt + Len(delim1)  
  'Then look past there for the second delimeter  
  valLen = InStr(startPos, textStr, delim2) - startPos  
  'The value we're looking for is between the two delimeters  
  GetCmdLineValue = Mid( textStr, startPos, valLen)  
Else  
  GetCmdLineValue = ||  
End If  
  
End Function

After you've entered all of the code into the agent, be sure to test the results by pasting the service URL (using your own server and database values) into the desktop browser.


Next Post: Macintosh OS Developer Friendly Tools

Previous Post: RIM Update

If this content helps you in some way, please consider buying me a coffee.

Header image: Photo by Tamara Gak on Unsplash.