级别: 中级 Brian O'Donovan, 项目主管, IBM
2009 年 8 月 10 日 通过本文了解如何在 Lotus Sametime Connect 工具箱的早期开发环境中,使用由 Java™ 工具箱公开的底层 IBM® Lotus® Sametime® Protocol API 的全部功能。通过扩展 BuddyNote 插件,即 Lotus Sametime 软件开发工具箱(SDK)提供的样例插件之一,您可以使用 Server Storage API 将好友通知存储在 Lotus Sametime 服务器上,而不是存储在一个本地配置文件中。
IBM Lotus Sametime Java 工具箱提供了对 Lotus Sametime Virtual Places (VP) 协议的直接访问,而 Lotus Sametime Connect 工具箱使您能够利用并扩展 Lotus Sametime 客户机的用户界面(UI)。
通常来讲,您需要从这两种工具箱中选择其中之一。但是,有时候您需要同时使用这两者。这实现起来有一些麻烦,因为您必须同时使用两种编程风格。
本文给出了结合使用这两种工具箱的一个实际示例,该示例扩展了 Lotus Sametime Connect 工具箱附带的 BuddyNote 样例,从而可以将好友通知存储到 Lotus Sametime 服务器上,而不是本地文件系统中。该示例必须使用 Lotus Sametime Java 工具箱中提供的 Server Storage API,但是 Lotus Sametime Connect 工具箱中未提供对应的内容。
将两种工具箱组合在一起非常复杂,部分原因是因为它们使用两种稍微不同的编程风格。使用 Lotus Sametime Connect 工具箱开发的插件实现并注册了一个消息处理对象,该对象扩展了 DefaultMessageHandler 类型。当发生任何需要通知给您的程序的事件时,平台将调用这个消息处理对象的消息处理功能。
这种方法的弊端是,消息处理程序将被多次调用来向您通知事件,而这些事件可能与您的插件无关。因此,消息处理程序应当首先检查由平台传入的事件,如果该事件与您的插件无关,那么立即返回此事件。
另一方面,Lotus Sametime Java 工具箱为每一种公开的服务提供了各种不同的侦听器接口。使用某个特定服务的程序必须为该服务实现并注册相关接口。侦听器只有在发生与服务有关的事件时才会得到调用。
本文描述的扩展后的插件有一个非常显著的优势,那就是您可以在随后从另一台客户机登录后查看您的通知。然而,最主要的优点是通过演示这个执行过程,对于任何需要同时使用两种 Lotus Sametime 工具箱的情形,您都可以轻松地应用相同的技巧。
Server Storage 服务 API 如何工作
Lotus Sametime Server Storage 服务是一个简单但强大的服务,服务器使用它存储每个用户的一组属性;这些属性可以在用户下次登录时进行查询(很可能从另一台客户机登录)。
大部分与 Lotus Sametime 客户机有关的用户设置都存储在客户机所在系统的本地硬盘中。如果使用多个客户机系统访问 Lotus Sametime,那么您所设置的大部分偏好设置对于每种客户机安装都是惟一的。但是,很多情况下,您可能希望无论您使用哪台客户机连接到 Lotus Sametime,系统都能记住这些偏好设置。
比如,默认情况下,好友列表的内容以备份形式存储在服务器中,以防您忘记好友的身份。每次连接到 Lotus Sametime 服务器时,客户机将对本地存储的好友列表内容与服务器上存储的内容进行比较,并且您有机会选择希望使用哪一个版本的好友列表。
好友列表实际上只是一个比较长的属性列表,应该存储在服务器中。每个属性通过一个整数键标识(好友列表的整数键为 0),这样您就可以知道应当显示哪个属性。
如果希望引入一个新属性,那么应当挑选一个还没有指定给其他属性的标识符键。Lotus Sametime 产品使用的属性键列表列出在 Lotus Sametime Java Toolkit Developer's Guide 的附录 C 中,包含在 Lotus Sametime SDK 的 client/stjava/doc 子目录下。
每个已保存的属性是一个简单的字节数组,但是可以将该属性转换为 Boolean、整数或字符串属性。对于好友列表,所含属性是一个包含好友列表内容的 XML 表示的字符串(具体的 XML 结构取决于使用的 Lotus Sametime 的版本)。
Server Storage 服务的接口是异步的,这与 Lotus Sametime 平台的大部分组件是一样的。当需要查询存储在服务器中的属性的值时,可以调用 StorageComp 对象的 queryAttr 方法。
但是,该函数并不返回属性的值;相反,它返回一个整数请求 ID。如果希望知道属性的值,必须注册一个实现 StorageServiceListener 接口的侦听器对象。
对象的 attrQueried 方法在完成任何属性查询后得到调用,并且必须检查传入函数的事件的请求 ID,以确保它匹配调用 queryAttr 方法返回的值。通过执行这种检查,可以确保返回的数据是对请求的响应,而不是对其他插件的响应。
配置开发环境
执行以下步骤,为本文的插件开发做准备:
- 如果还没有在系统中安装 Lotus Sametime 客户机,那么需要安装它(下载 Lotus Sametime 8.0 客户机试用版)。本文使用的是 Lotus Sametime 8.0.2,但是您也可以使用版本 7.5 或任何更新版本。
获得与您使用的 Lotus Sametime 版本匹配的 Lotus Sametime 工具箱。
- Lotus Sametime 工具箱下载中包含一些可用于不同类型项目的工具箱。这里我们使用 Lotus Sametime Connect 工具箱,它位于所下载的 ZIP 文件的 client\connect 子目录中。
- 在 doc 子目录中,找到 Stxxx_Integration_Guide.pdf 文件,其中 XXX 表示所使用的 Lotus Sametime 的版本。遵循 Chapter 4 中的说明配置您的开发环境。
- 可能还需要参考 Lotus Sametime Java 工具箱的文档,该文档位于 SDK 的 client/stjava/docsubdirectory 中。
导入 BuddyNote 样例
我们并不准备从头开始开发插件,相反,将扩展下载的工具箱中附带的 BuddyNote 样例。在继续阅读本文主要内容之前,让我们先花些时间来了解一下这个附带的样例。
首先,在 BuddyNoteView 对象的 retrieveBuddyNote 和 storeBuddyNote 方法中设置断点,在调试模式下启动 Lotus Sametime,然后观察何时调用这些函数。
注意 retrieveBuddyNote 方法是在好友列表中选中了一个好友后得到调用。它将读取存储在本地配置文件中的有关该好友的通知(如果有的话)。如果在 BuddyNote 视图面板中输入一个通知,storeBuddyNotefunction 将被调用,用来将输入的通知保存到一个本地配置文件中,供日后进行检索。
本文介绍了如何使用其他代码替换这些函数,从而能够将这些通知保存到 Lotus Sametime 服务器上;然而,由于大部分插件代码未进行改动,因此应当简单了解一下其余代码,以便理解代码的工作原理。
为 Lotus Sametime 插件设置开发环境是一件困难的任务,因此很容易出错。如果插件不能按预期工作,那么现在应当花些时间进行调试并解决问题,因为如果核心插件不工作的话,那么扩展工作就无法继续进行。
为项目添加 Lotus Sametime 工具箱 API
要使用 Lotus Sametime Java 工具箱中的 Server Storage API,需要向项目定义的依赖关系部分添加包含该 API 的相关插件。为此,执行以下步骤:
- 单击名为 manifest.mf 的文件,该文件位于插件项目的 META-INF 目录中。Eclipse 将在一个特殊的选项卡式编辑器中打开该文件。
- 单击 Dependencies 选项卡,然后单击 Add 按钮。
- 添加如下两个插件:
com.ibm.collaboration.realtime.stjavatk
com.ibm.collaboration.realtime.rtc.core
为每个社区创建 BuddyNoteServerStorage 对象
我们在此编写的代码块位于名为 BuddyNoteServerStorage 的对象中。您应当使用 Eclipse 向导来创建对象并表明该对象实现 StorageServiceListener 接口。要实现此目的,最简单的方法是执行以下步骤:
- 右键单击 package Explorer 模板中的 com.ibm.collaboration.realtime.sample.buddynote 包。
- 从弹出菜单中选择 New\Class 选项。
- 在显示的窗口中,指明您希望 Eclipse 创建继承的抽象方法。
对于 Lotus Sametime,可以连接到多个社区服务器,因此您可以为存储与好友相关的通知(与该社区有关)的所有社区创建一个 BuddyNoteServerStorage 对象实例:
- 使用以下代码行添加一个静态映射,跟踪为每个社区发出的实例:
private static Map<String,BuddyNoteServerStorage>
listenerMap = new HashMap<String, BuddyNoteServerStorage>();
- 要确保为每个社区只创建一个对象,不应该允许人们直接调用构造器。相反,创建一个包装器函数,它返回现有对象的句柄,或者创建一个新对象并添加到映射中,如清单 1 中的代码所示。
清单 1. 包装器函数代码
public static BuddyNoteServerStorage getStorageObject (String cID) {
BuddyNoteServerStorage bss =
(BuddyNoteServerStorage)listenerMap.get(cID);
if (null == bss) {
bss = new BuddyNoteServerStorage (cID);
}
return bss;
}
|
- 该对象需要使用一些字段保存值,因此下一步是编写构造器例程本身。构造器获得一个参数,该参数就是为其创建对象的社区的 ID。应当将此构造器标识为私有,这样就不会意外调用构造器并忘记将它记录到对象映射中。使用清单 2 的代码实现构造器例程。
清单 2. 构造器例程
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();
}
}
|
下面详细解释一下这个构造器的代码:
- 前面的几行代码主要用于为社区获得 StorageComp 对象的句柄,Lotus Sametime Integration Guide 的附录 D 对此作了表述。由于该句柄将用于对象的某些位置,因此将它保存在一个私有字段中,而不是方法变量中。
- 获得了 StorageComp 对象的句柄后,我们使用它将新类注册为 Server Storage 服务的侦听器。
- 接下来,我们将这个对象添加到 listenerMap,从而可以在稍后实现重用。
- 最后,发出一个请求来查询 Buddy Notes 属性的值,其键编号为 90000。通过向 Sametime_ID_Request@lotus.com 发送电子邮件,我们为存储好友通知保留了这个键编号。
- 如果希望保存新的属性类型,应当向这个邮件地址发送一个通知,确保您目前用于标识属性的键没有被其他人使用。
- 对 queryAttr 的调用不会返回任何实际数据,而只返回 ID 编号。我们将此 ID 编号存储到一个私有字段中,这样当一个响应到达时,我们就可以发现它。
- 由于此代码仅仅作为样例使用,我们没有执行任何实际的错误处理,但是我们实现了一个虚构的异常处理程序,从而完成代码的编译。
由于存储服务是异步操作的,因此在与 Lotus Sametime 社区建立连接后,应当立即从 Server Storage 服务请求被保存的好友通知的一个副本,而不是一直等待直到实际需要某个好友通知。
为此,将以下代码行添加到 BuddyNoteMessageHandler.java 文件中处理 ImConnectedMessage 消息类型的例程中:
String community = message.getCommunityId();
BuddyNoteServerStorage.getStorageObject(community);
目前为止,我们所做的就是请求服务器上好友通知信息的一个副本。下一步是实现在接收消息后处理消息的例程。
在第一次创建 BuddyNoteServerStorage 类时,Eclipse 向导自动创建名为 attrQueried 的虚构方法。在服务器响应您发出的好友通知信息副本的请求时,将调用此函数。
应当使用清单 3 的代码替换这个虚构的函数。
清单 3. 用于替换虚构函数的代码
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);
}
}
}
|
这段代码的操作方式如下:
- 首先,检查请求 ID 码;如果与先前提供的请求代码不匹配,那么事件必然响应的是另一个插件的请求,因此要忽略它。
- 接下来,检查结果代码,看看这个事件是否与成功的查询有关。如果这个代码是生产插件,那么需要实现错误代理,但是由于这里仅仅是一个编程示例,因此我们忽略了所有失败。
- 最后,解析返回的字符串以判断通知与好友之间的具体关联。清单 4 中的代码实现了一个简单的解析器,它假设我们使用冒号分隔好友 ID 和相关的通知,并使用分号分隔后续通知。
清单 4. 简单的解析器
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]);
}
}
|
生产插件将需要一个更复杂的形式,或者至少可以处理内部包含分号的通知。
这个通知解析例程必须与将好友通知以期望格式保存在服务器中的类似例程匹配。清单 5 中的代码实现了这个目的,它首先将存储的通知添加到好友列表中,然后创建一个字符串来表示整个好友列表,随后请求将此字符串保存在服务器中。
如前所述,我们不会实现任何错误处理功能,因为这与本文的重点无关。
清单 5. 在服务器上存储 BuddyNotes 的例程
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);
}
|
该插件几乎已经全部完成。剩下惟一要做的是修改 BuddyNoteView.java 中的代码,使它调用新的功能,如下所示:
- 首先,通过调用前面编写的 BuddyNoteServerStorage.storeBuddyNote 方法替换对 storeBuddyNote 方法的调用。
- 修改调用 retrieveBuddyNote 方法的代码,使它改为调用 BuddyNoteServerStorage.retrieveBuddyNote 函数(参见清单 6)。
- 删除私有方法 retrieveBuddyNote、storeBuddyNote 和 getLocalFileSpec,因为它们不再有用。
清单 6. 从服务器中检索已保存 BuddyNotes 的例程
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;
}
|
单步执行代码
最好在这个插件的关键函数中设置一些断点,然后在调试模式下启动 Lotus Sametime,检查它的工作方式。可能还需要在插件中改进错误处理功能。
结束语
学习了本文的示例之后,您应当已经了解如何在同一个程序中同时使用 Lotus Sametime Java 工具箱和 Lotus Sametime Connect 工具箱。尽管混合使用两种编程方法会为程序添加复杂性,但是将这两种工具箱组合起来有时是必须的。本文介绍的知识可以帮助您结合使用这两种工具箱来解决其他挑战。
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Brian O'Donovan 具有爱尔兰都柏林市的 Trinity College 的计算机科学博士学位,在计算机行业具有超过 25 年的经验。他目前为 IBM Dublin Software Lab 工作,负责领导团队开发 IBM Lotus Sametime 产品系列。 |
对本文的评价
|