Using the IBM Lotus Sametime Java toolkit from within a Lotus Sametime Connect toolkit plug-in

Learn how to use the full power of the low-level IBM® Lotus® Sametime® Protocol API exposed by the Java™ toolkit from within the easier development environment of the Lotus Sametime Connect toolkit. By extending the BuddyNote plug-in, which is one of the sample plug-ins provided with the Lotus Sametime software development kit (SDK), you can use the Server Storage API to store the notes about your buddies on the Lotus Sametime server rather than in a local configuration file.

Brian O'Donovan (brian_odonovan@ie.ibm.com), Program Director, IBM

Brian O'Donovan has a Ph. D. in computer science from Trinity College, Dublin, Ireland, and more than 25 years of experience working in the computer industry. He is currently based in the IBM Dublin Software Lab, where he manages the team that develops the IBM Lotus Sametime family of products. You can reach him at brian_odonovan@ie.ibm.com.



29 June 2009 (First published 02 June 2009)

Also available in Chinese Spanish

Editor's note: Know a lot about this topic? Want to share your expertise? Participate in the IBM Lotus software wiki program today.

Introduction

The IBM Lotus Sametime Java toolkit offers direct access to the Lotus Sametime Virtual Places (VP) protocol, while the Lotus Sametime Connect toolkit lets you leverage and extend the user interface (UI) of the Lotus Sametime client.

Normally, you choose one of these toolkits or the other. There may be circumstances, though, in which you need to use both toolkits at the same time. This approach is possible but a little tricky because you must use two programming styles simultaneously.

This article presents a practical example of how to use both toolkits together by extending the BuddyNote sample that ships with the Lotus Sametime Connect toolkit so that it stores the buddy notes on the Lotus Sametime server instead of on the local file system. This example necessitates using the Server Storage API that is present in the Lotus Sametime Java toolkit but has no equivalent in the Lotus Sametime Connect toolkit.

Part of the complexity of mixing the toolkits occurs because they use two subtly different programming styles. Plug-ins that are developed with the Lotus Sametime Connect toolkit implement and register a single message processing object that extends the DefaultMessageHandler type. The platform calls the message handling function of this message processing object whenever any event occurs that your program might possibly want to be notified about.

The trouble with this approach is that your message handler is called many times to notify you of events that are not relevant for your plug-in. The first step that your message handler should take is to examine the event passed in from the platform and return it immediately if it is not relevant.

The Lotus Sametime Java toolkit, on the other hand, has a number of distinct listener interfaces for each of the exposed services. A program that uses a particular service must implement and register the relevant interface for the service. The listener is called only when events relevant to that service occur.

A significant benefit of the extended plug-in described in this article is that it lets you see your notes when you later log in from another client. The main benefit, however, is that by examining how this is done, you can easily apply the same techniques to any other situation in which you need to use both Lotus Sametime toolkits simultaneously.


How the Server Storage service API works

The Lotus Sametime Server Storage service is a simple yet powerful service whereby the server stores a list of attributes for each user; these attributes can then be queried when the user next logs on (possibly from another client).

The majority of user settings relating to the Lotus Sametime client are stored on the local hard disk of the system on which the client is installed. If you use more than one client system to access Lotus Sametime, most of the preferences that you set up are unique to each client installation. There are a number of situations, though, in which you might like to remember the preferences, regardless of which client you happen to use to connect to Lotus Sametime.

For example, by default the contents of your buddy list are stored on the server as a backup, in case you forget the identity of your buddies. Each time you connect to a Lotus Sametime server, the client compares the contents of your locally stored buddy list with the version that is stored on the server, and you are given the opportunity to choose which version you want to use.

The buddy list is actually only one of a potentially long list of attributes that might be stored on the server on your behalf. Each attribute is identified by an integer key (zero, in the case of the buddy list) so that you can know what the attribute is supposed to represent.

If you want to introduce a new attribute, you should pick an identifier key that is not already assigned to another attribute. A list of the attribute keys used by the Lotus Sametime product itself is listed in Appendix C of the Lotus Sametime Java Toolkit Developer's Guide, which is contained in the client/stjava/doc subdirectory of the Lotus Sametime SDK.

Each stored attribute is a simple array of bytes, but it's possible to interpret the attribute as a Boolean, integer, or string attribute. In the case of the buddy list, the attribute stored is a string containing an XML representation of the contents of your buddy list (the exact structure of the XML depends on the version of Lotus Sametime being used).

The interface to the Server Storage service is asynchronous, in common with most parts of the Lotus Sametime platform. When you want to query the value of an attribute stored on your behalf on the server, you call the queryAttr method of the StorageComp object.

This function, though, does not return the value of the attribute; instead, it returns an integer request ID. If you want to know the value of the attribute, you must register a listener object that implements the StorageServiceListener interface.

The attrQueried method of your object is called whenever any attribute query is completed, and you must check the request ID of the event passed to this function to ensure that it matches the value returned by the call to the queryAttr method. By doing this check, you ensure that the data being returned is a response to your request and not a response to a different plug-in.


Configuring your development environment

Follow these steps to prepare for developing the plug-in described here:

  1. If you don't already have the Lotus Sametime client installed on yoursystem, you need to install it (download a trial version of the Lotus Sametime 8.0 client). Here we used Lotus Sametime 8.0.2, but you should be able to follow along using version 7.5 or any later version.

    Obtain the Lotus Sametime toolkit that matches the version of Lotus Sametime that you are using.

  2. The Lotus Sametime toolkit download contains several toolkits that you can use for different types of projects. Here we use the Lotus Sametime Connect toolkit, which is located in the client\connect subdirectory of the ZIP file that you downloaded.
  3. In the doc subdirectory, locate a file named Stxxx_Integration_Guide.pdf, where XXX is the version of Lotus Sametime that you are using. Follow the instructions in Chapter 4 to configure your development environment.
  4. You might also want to consult the documentation for the Lotus Sametime Java toolkit, which is in the client/stjava/docsubdirectory of the SDK.

Importing the BuddyNote sample

Instead of developing a plug-in from scratch, you can extend the BuddyNote sample that is included with the toolkit that you downloaded. Before you dive into the main body of this article, let's spend some time reviewing the existing sample.

To start, set breakpoints in the retrieveBuddyNote and storeBuddyNote methods of the BuddyNoteView object, launch Lotus Sametime in debug mode, and then watch when these functions are called.

Notice that the retrieveBuddyNote method is called whenever a buddy is selected on your buddy list. It reads what note (if any) is stored about this buddy in a local configuration file. If you type a note into the BuddyNote view pane, the storeBuddyNotefunction is called to store the note that you typed in a local configuration file for later retrieval.

This article describes how you can replace these functions with alternative code that stores the notes on the Lotus Sametime server; however, because the bulk of the remainder of the plug-in is left unchanged, you should briefly examine the rest of the code to verify that you understand how it works.

Setting up the development environment for Lotus Sametime plug-ins can be a tricky task, and it's easy to make a mistake. If the plug-in does not work as expected, you should spend a little time now to debug and solve the problem because there's no hope of getting the extensions to work if the core plug-in does not work.


Adding the Lotus Sametime toolkit APIs to your project

To use the Server Storage API that is part of the Lotus Sametime Java toolkit, you need to add the relevant plug-ins that contain the API to the dependencies section of your project definition. To do this task, follow these steps:

  1. Click the file named manifest.mf, which is in the META-INF directory of your plug-in project. Eclipse opens the file in a special tabbed editor.
  2. Click the Dependencies tab, and then click the Add button.
  3. Add the following two plug-ins:

    com.ibm.collaboration.realtime.stjavatk
    com.ibm.collaboration.realtime.rtc.core

Creating the BuddyNoteServerStorage object for each community

The bulk of the code that we write here is in an object called BuddyNoteServerStorage. You should use the Eclipse wizard to create the object and specify that it implements the StorageServiceListener interface. The easiest way to do this step is to follow these steps:

  1. Right-click the com.ibm.collaboration.realtime.sample.buddynote package in the package Explorer pane.
  2. Select the New\Class option from the pop-up menu.
  3. In the window that displays, specify that you want Eclipse to create the inherited abstract methods.

With Lotus Sametime, it is possible to connect to multiple community servers, so we create an instance of the BuddyNoteServerStorage object for each community that stores the buddy-related notes associated with that community:

  1. Use the following line of code to add a static map that keeps track of which instance is issued for each community:

    private static Map<String,BuddyNoteServerStorage>
    listenerMap = new HashMap<String, BuddyNoteServerStorage>();
  2. To ensure that you have only one object created for each community, you should not allow people to call the constructor object directly. Instead, create a wrapper function that either returns a handle to the existing object for a particular community or creates a new object and adds it to the map, using the code in listing 1.
Listing 1. Wrapper function code
public static BuddyNoteServerStorage getStorageObject (String cID) {
   BuddyNoteServerStorage bss =
      (BuddyNoteServerStorage)listenerMap.get(cID);
   if (null == bss) {
      bss = new BuddyNoteServerStorage (cID);
   }
   return bss;
}
  1. The object needs a few fields to store values, so the next step is to write the constructor routine itself. Your constructor takes a parameter that is the ID of the community for which this object is being created. You should mark this constructor private so that you can't accidentally call the constructor and forget to record it in your object map. Use the code in listing 2 for the constructor routine.
Listing 2. Constructor routine
private StorageComp _sserv;
private int _reqID;
	private final static int BUDDY_NOTES_ATT_KEY = 90000;

private BuddyNoteServerStorage (String communityID) {
   try {
      CommunityService commSvc = (CommunityService)   
         ServiceHub.getService(CommunityService.SERVICE_TYPE);
      Community community = commSvc.getCommunity(communityID);
      RtcSession rtcSession = community.getRtcSession();
	STSession sess = (STSession)   
                        rtcSession.getProtocolSession();
       _sserv = (StorageComp)
                 sess.getCompApi(StorageService.COMP_NAME);

	_sserv.addStorageServiceListener(this);

      listenerMap.put(communityID, this);

	_reqID = _sserv.queryAttr(BUDDY_NOTES_ATT_KEY)).intValue();
   } catch (ServiceException e) {
      e.printStackTrace();
   }
}

The code in this constructor can be explained as follows:

  • The first few lines are concerned with getting a handle to the StorageComp object for this community, as described in Appendix D of the Lotus Sametime Integration Guide. Because this handle is needed elsewhere in the object, we store this handle in a private field rather than a method variable.
  • After we have the handle to the StorageComp object, we use it to register the new class as a listener for the Server Storage service.
  • Next we add this object to the listenerMap so that it can be reused later.
  • Last, we issue a request to query the value of the Buddy Notes attribute, which has the key number 90000. We reserved this key number for storing buddy notes by sending an email to Sametime_ID_Request@lotus.com.

    • If you want to store a new attribute type, you should also send a note to this email address, to ensure that the key that you are using to identify your attribute is not already being used by someone else.
  • The call to queryAttr won't return us any real data, just a request ID number. We store the request ID number in a private field so that when a response arrives we can recognize it.
  • Because this code is only a sample we don't perform any real error processing, but we do implement a dummy exception handler to allow the code to compile.

Because the storage service operates asynchronously, it makes sense to request a copy of the stored buddy notes from the Server Storage service as soon as you connect to a Lotus Sametime community, rather than waiting until a buddy note is actually required.

To do this step, add the following lines of code to the routine in the BuddyNoteMessageHandler.java file that deals with the ImConnectedMessage message type:

String community = message.getCommunityId();
BuddyNoteServerStorage.getStorageObject(community);

So far, all that we've done is request a copy of the buddy note information on the server. The next step is to implement the routine that deals with the information when it is received.

The Eclipse wizard automatically created a dummy method named attrQueried when you initially created the BuddyNoteServerStorage class. This function is called when the server responds to your request for a copy of the buddy note information.

You should replace the dummy function with the code in listing 3.

Listing 3. Code to replace dummy function
public void attrQueried(StorageEvent evt) {
   int id = evt.getRequestId().intValue();
   if (id == this._reqID) {
      int result = evt.getRequestResult();
	if (STError.ST_OK == result) {
         Vector v = evt.getAttrList();
         STAttribute attrib = (STAttribute) v.get(0);
	   String buddyNoteStr = attrib.getString();
         parseBuddyNotes (buddyNoteStr);
	}
   }
}

This code operates as follows:

  • First, we check the request ID code; if it does not match the request code we were given earlier, the event must be a response to another plug-in's request, and so we ignore it.
  • Next, we check the result code to see if this event relates to a successful query. If this code were a production plug-in, we might want to implement error handling, but because this is just a programming example, we ignore any failures.
  • Finally, we parse the returned string to determine which note is associated with which buddy. The code in listing 4 implements a crude parser that assumes that we use colons to separate buddy IDs from the associated note and semicolons to separate subsequent notes.
Listing 4. Crude parser
private void parseBuddyNotes(String buddyNoteStr) {
   String [] buddyNotes = buddyNoteStr.split(";");
   for (int i=0; i<buddyNotes.length; i++) {
      String [] noteParts = buddyNotes[i].split(":");
      if (2 == noteParts.length)
         this._noteMap.put(noteParts[0], noteParts[1]);
   }	
}

A production plug-in would need a more complex format or at least a way to handle notes containing semi-colons within them.

This note-parsing routine must be matched with a similar routine that stores the buddy notes on the server in the expected format. The code in listing 5 achieves that goal by first adding the stored note to the buddy list, then creating a string to represent the entire buddy list, and then requesting that this string is stored on the server.

As mentioned earlier, we won't bother implementing any error handling because it is not relevant to the core of this article.

Listing 5. Routine to store BuddyNotes on the server
public static void storeBuddyNote(Person p, String s) {
   String pID = p.getContactId();
   String cID = p.getCommunityId();
		
   BuddyNoteServerStorage bss = getStorageObject(cID);
   bss._noteMap.put(pID,s);
   String buddyNoteStr = "";
   Iterator<String> nameIt = bss._noteMap.keySet().iterator();
   while (nameIt.hasNext()) {
      String buddyName= nameIt.next();
      String buddyNote = bss._noteMap.get(nameIt);
      buddyNoteStr += buddyName + ":" + buddyNote;
      if (nameIt.hasNext())
         buddyNoteStr += ";";

   }

   STAttribute attribute = 
         new STAttribute(BUDDY_NOTES_ATT_KEY, buddyNoteStr);
   bss._sserv.storeAttr(attribute);
}

The plug-in is now almost complete. The only remaining task is to alter the code in BuddyNoteView.java so that it calls the new functionality, as follows:

  1. First, replace the call to the storeBuddyNote method with a call to the BuddyNoteServerStorage.storeBuddyNote method that you wrote previously.
  2. Change the line of code that calls the retrieveBuddyNote method so that it instead calls the BuddyNoteServerStorage.retrieveBuddyNote function (see listing 6).
  3. Delete the private methods retrieveBuddyNote, storeBuddyNote, and getLocalFileSpec because they are no longer needed.
Listing 6. Routine to retrieve stored BuddyNotes from the server
public static String retrieveBuddyNote(Person p) {
   String pID = p.getContactId();
   String cID = p.getCommunityId();

   BuddyNoteServerStorage bss = getStorageObject(cID);
   String storedNote = (String) bss._noteMap.get(pID);
   if (null == storedNote)
      return ("No note stored on the server for " + pID);
   else
      return storedNote;
}

Stepping through your code

It's a good idea to set some breakpoints in the key functions in this plug-in and then launch Lotus Sametime in debug mode, to view how it works. You might also want to improve the error handling in the plug-in.


Conclusion

After working through the example described in this article you should understand how to use both the Lotus Sametime Java toolkit and the Lotus Sametime Connect toolkit in a single program. Although mixing the two programming methods can add complexity to your program, the combination of the two toolkits working together is sometimes required. The lessons that you learned here can help you use the combination of both toolkits to solve other challenges.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into IBM collaboration and social software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Lotus
ArticleID=392551
ArticleTitle=Using the IBM Lotus Sametime Java toolkit from within a Lotus Sametime Connect toolkit plug-in
publish-date=06292009