IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Lotus  >

Lotus Sametime Toolkit 开发入门,第 3 部分: 使用 Sametime Connect Toolkit 开发 Logging Bot

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


级别: 中级

刘 欣 (liuxinlx@cn.ibm.com), 软件工程师,IBM CSDL
常 红平 (changhp@cn.ibm.com), 软件工程师,IBM CSDL

2008 年 2 月 29 日

在本系列的前两篇文章中,我们分别介绍了使用 Sametime 客户端 Toolkit 和服务器端 Toolkit 来开发应用程序,扩展 Sametime 的功能。从 Sametime 7.5 开始,Sametime 的客户端软件开始构建于 Eclipse 平台之上,得益于灵活的 plugin 体系结构,Sametime 平台也变得更加开发。 同时 Toolkit 家族中又加入了一个新的成员 Sametime Connect Toolkit, 专门提供给开发人员进行 Sametime Plugin 的开发。本文将要介绍的是利用 Sametime Connect Toolit 和 Java Toolkit 一起来开发一个 Logging Bot 的应用程序。

本文假定读者已经阅读了 第一篇 文章,并且具备初步的 Eclipse Plugin 开发和 SWT 的知识。

Sametime Toolkit 简介

我们在 第一篇 文章中介绍过 Toolkit 家族中的各个组成部分,IBM Lotus Sametime Connect 7.5 对于之前的版本而言,是一个巨大的改变。这一版本开始构建于 Eclipse 平台之上,Sametime Connect 不仅仅是一个即时通信的客户端程序,更重要的是一个开放而灵活的应用程序平台,根据业务的需要,新的应用能够以插件的形式被集成的 Sametime Connect 中。

为了帮助开发人员充分利用该平台的能力,IBM 提供了 Sametime Connect Toolkit 来开发 Eclipse plug-in ,扩展 Sametime connect client。该 Toolkit 包含以下内容:

  • Integration Guide
  • Javadoc
  • J9 JCL Desktop runtime
  • Sample plug-ins

Sametime Logging Bot 概述

简单的来讲,Sametime logging bot 分为3部分:

  • Logging Bot Plugin:这是一个运行在开发者 Sametime 客户端的一个插件,它能够通过 Sametime 技术和服务器端的 Logging Bot 进行交流。 用户在客户端可以选择订阅某一个特定 Web 应用程序的日志,也可以发送一些具体的命令给 Logging Bot Server。参见 图1 的蓝色部分。
  • Logging Bot Server :这是一个用 Sametime Java Toolkit 开发的"机器人"程序,它接收并且缓存 Web 应用程序发送过来的日志。然后能够通过"推"的方式把相关的信息发送给 Logging Bot Plugin。 同时它也接受用户通过 Logging Bot Plugin 发送的命令。参见 图1 的紫色部分。
  • Web 应用程序:普通的 Java Web 应用程序,如果它出了某些错误,可以调用 Logging Bot 的接口来写日志。参见 图1 的绿色部分。


图 1. 体系结构
体系结构

Logging Bot Plugin 用户界面参见 图2。它是运行在 Sametime Connect 中的一个 Plugin, 以应用程序面板的形式显示 Log 的信息。在图2中,可以看到有一个 Tab 页面包含了两个应用程序:OnlineTraing 和 PetStore。这些应用程序信息是 Plugin 在启动时向 Server 发出请求获取的。如果用户对某个应用程序选择"Subscribe"从而订阅该应用的 Log,那么在该 Tab 页面中就会显示从服务器发回来的 Log。


图 2. 用户界面
用户界面

建立 Logging Bot 开发环境

在 Eclipse 中建立 Plugin 开发环境

在RedBook<<Extending Sametime 7.5 Building Plug-ins for Sametime>>中,有专门的章节"Setting up the Integrated Development Environment" 来描述如何建立开发环境, 本文不再赘述,请读者参见参考资源部分。

申请一个账号用来测试

为创建 Logging Bot,需要为它申请一个 Sametime ID。在本文中,对于我们的测试和开发,可以使用 IBM Lotus Sametime 7.5 Demonstration Site。我们的 LoggingBot ID 是: logging_bot@dfw.ibm.com。同时读者也需要为自己申请一个账号来模拟客户端的用户。





回页首


Loging Bot Plugin 和Server的通信协议

如何发送接收 Log

体系结构图中,我们可以看出对应于每一个Web应用程序,客户端的每一个 Plugin 都有可能去获得它的 Log,那么对于 Logging Bot Server 来说需要维护这样一个数据结构:

  • Web 应用程序 1
    • 用户 1
    • 用户 2
    • ... ...
  • Web 应用程序 2
    • 用户 1
    • 用户 2
    • ... ...
  • Web 应用程序 3
    • ... ...
    • ... ...
    • ... ...

采用这种设计,当 Web 应用程序写 Log 的时候,Logging Bot Server 就知道向那些用户去发送 Log。 当客户端不再订阅某个应用程序的 Log 时,这个应用程序的列表就会发生变化:把某个用户从应用程序的用户列表中删除。

在 Logging Bot Server 中,我们可以自己编写代码来实现这样一个数据结构,并且可以根据用户是否订阅来动态调整,同时我们还需要编写大量代码来向各个用户来发送消息。实际上,在 Sametime Java Toolkit 中已经提供了一个非常强大的功能:Place。Place是一个用来进行协作的虚拟空间,用户可以进入和离开这个 Place,进入 Place 以后,就可以非常方便的向整个 Place 的所有成员发送消息,读者想知道更多细节的话。可以参考该系列的第一篇文章,参见 参考资源

利用 Place 的特性,可以极大的简化我们的代码:我们可以为每个 Web 应用建立这样一个虚拟的 Place,Logging Bot Server 登陆以后,首先进入各个 Place,然后等待 Web 应用程序来发送日志。客户端的 Logging Bot Client 登录以后,根据用户的选择(是否订阅某个应用的 Log)来选择进入不同的 Place。 当 Web 应用程序写日志时,Logging Bot Server 可以向整个 Place 广播该日志,位于该 Place 的所有成员都可以收到。 如果某个用户选择不再订阅 Web 应用的日志,只需要简单的离开该 Place 就可以了。这样一来,系统中所有复杂的状态维护和消息的群发都交给系统级 Place 来处理,我们的应用程序更加简单、清晰、稳定。

如何发送命令

客户端 Plugin 除了能被动的接受 Log信息以外,还能够主动的向 Logging Bot Server 发出命令来获取自己感兴趣的信息,在本文中,我们只定义了一个命令:获得当前的应用程序列表。客户端在登录以后,可以向 Logging Bot Server 发出该命令,在拿到列表以后,客户端 Plugin 就可以创建一个 Tab 页面来显示所有的应用程序日志。

在实现这个功能时,我们可以使用 Sametime Java Toolkit 提供的 InstanceMessingService,该 Service 提供了一个虚拟的通道可以让 Logging Bot Client 和 Server 进行通信。Client 可以通过该通道向 Server 发送命令(以文本消息的形式),Server 接收到以后对命令进行解析和响应。





回页首


开发 Logging Bot Server

Logging Bot Server 提供了三个重要的功能:

  • 提供接口给 Web 应用程序来写日志
  • 向客户端 Plugin 发送日志
  • 接收客户端发送的命名并进行处理和响应

在介绍这个三个功能之前,先来看看 Logging Bot 是如何进行初始化的。

初始化

初始化的第一步就是 Login, 它的工作方式在第一篇文章《使用客户端 Toolkit 与 Sametime 服务器交互》(参见 参考资源 部分)中已经描述的非常详细了,这里不再详述,我们需要建立一个 LoginListener 来处理 Loging 成功的事件,代码如下:


清单1:Logging Bot Server初始化
                
public void init() {
    try {
        session = new STSession("Logging Bot" + this);
        session.loadSemanticComponents();
    } catch (DuplicateObjectException e) {
        e.printStackTrace();
    }
    session.start();
    appNames = getAppNameList();        
    Iterator iter = appNames.iterator();        
    while(iter.hasNext()){
        String appName = (String)iter.next();
        LogBuffer buffer = new LogBuffer();
        this.logBuffers.put(appName, buffer);
    }
    login();
}			
private void login() {
        comm = (CommunityService) session
                .getCompApi(CommunityService.COMP_NAME);				
        comm.addLoginListener(new LoginHandler());				
        comm.loginByPassword(host, userId, password);
}			
public class LoginHandler implements LoginListener {
    public void loggedIn(LoginEvent event) {
        System.out.println("Logged in");			
        imService = (InstantMessagingService) session
                .getCompApi(InstantMessagingService.COMP_NAME);			
        imService.registerImType(LoggingBotConstant.IM_TYPE);			
        imService.addImServiceListener(new ImServiceHandler());
        Iterator iter = appNames.iterator();			
        while (iter.hasNext()) {
            String appName = (String) iter.next();
            Thread thread = new Thread(new PlaceRunable(appName));
            thread.start();                
        }			
    }			
    public void loggedOut(LoginEvent event) {
        System.out.print("Logged Out");
    }
}     

可以看出在 init 方法中,读取应用程序名称的列表,对每个应用程序,系统都会创建一个 LogBuffer 用来存放日志,它的用途在下一节“发送日志”中会详细描述。在 LoginHandler 中,首先创建一个 InstanceMessagingService ,该 Service 用来处理客户端 Plugin 发送的命令,需要注意的是我们调用 registerImType 方法注册了我们自己的消息类型,这样就不会和系统的消息类型冲突。

接下来为每个应用程序创建一个对应的 "Place", 用来发送 Log。我们为每个 Place 都生成一个线程 PlaceRunable:


清单2:PlaceRunable
                
class PlaceRunable implements Runnable{        
        private String appName;
        public PlaceRunable(String appName){
            this.appName = appName;
        }
        public void run() {            
            LogBuffer buffer = (LogBuffer)logBuffers.get(appName);
            LoggingBotServerPlace place = new LoggingBotServerPlace(
                    session, appName,buffer);
            places.put(appName,place);
            System.out.println("Loging Bot Place created for "
                    + place.getAppName());
        }
        
}     

每个 Place 都是一个 LoggingBotServerPlace 对象,我们把它放到一个 HashMap 方便以后的使用。在 LoggingBotServerPlace 中,需要做的初始化是:


清单3:Place 初始化
                
public LoggingBotServerPlace(STSession session,String appName,LogBuffer buffer) {
	this.session = session;
	this.appName = appName;
	this.buffer = buffer;
	PlacesService placesService = (PlacesService) session
			.getCompApi(PlacesService.COMP_NAME);
	// Create the place wer'e going to work with, and enter.
	place = placesService.createPlace(appName,
			appName, EncLevel.ENC_LEVEL_DONT_CARE, 0);
	place.addPlaceListener(new PlaceHandler());
	place.enter();
}   

在“如何发送和接收消息”一节中我们提到为了方便消息的处理,我们为每个应用程序都建立一个虚拟的 Place, 每个 Place 都以该应用程序名称命名,使用 PlaceService 可以创建这样一个 Place,创建以后注册一个监听器, 接下来就进入该 Place。监听器的细节不再详述,一旦成功的进入该 Place,就会设置 placeReady 标志为 true。这样外界就可以向该 Place 发送 Log 了。

发送日志

由于 Sametime Toolkit 的编程模型是异步的,即用户在调用某个操作之前,比如 login, 通常需要注册一个 LoginListener。 当调用了 Login 以后应用程序会立刻返回, 执行下一个指令。如果 Login 成功, LoginListener 的 loggedIn 方法会被调用。但是这个方法什么时候调用却是不可预料的。当 Web 应用程序写日志时,它可以调用 LoggingBot 提供的 writeLog 方法。但需要注意的是该方法被调用时,LoggingBot 本身很有可能还没有完成所有的初始化工作,比如还没有完成 login、place 还没有创建等等,但是我们又不能简单的把 Web 应用程序的日志抛弃。解决这个问题的办法就是先把日志放到一个 Buffer 中,当所有的初始化完成以后,每一个 LoggingBotServerPlace 就可以主动地获取属于自己的日志,然后向整个 Place 广播。


清单4:把日志写入 buffer
                
public void writeLog(String appName, String logLevel, String logMsg) {        
    if(!appNames.contains(appName)){
        System.out.println("The application:"+appName+"is not supported by Logging Bot");
        return;
    }
    LogBuffer buffer = (LogBuffer)logBuffers.get(appName);  
    LogMessage lm = new LogMessage();
    lm.level = logLevel;
    lm.msg = logMsg;
    lm.date = new Date();
    buffer.addLog(lm);
    
}   


清单5:发送 Log
                
public void sendLogs(){
    //如果Place没有准备好,当前线程休眠1秒后再试
    while(!this.placeReady){
        try {            
            Thread.sleep(1000);
        } catch (InterruptedException e) {                        
            e.printStackTrace();
        }
    }	    
    LogMessage log = null;
    while(true){
        log = buffer.getLog();
        //如果Log,当前线程休眠1秒后再试
        if(null == log){               
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {                        
                e.printStackTrace();
            }
        }
        else{
            //向整个Place广播该log
            place.sendText(log.toString());
        }
    }
}   

处理命令

在 Logging Server 初始化时,建立了一个 InstantMessagingService 用来监听客户端发送的命令,在本文所用的代码中只支持一个命令:LIST_PLACES,客户端发出该命令来获取应用程序列表,读者可以在如下的方法中加入自己的命令。


清单6:处理命令
                
public void textReceived(ImEvent e) {
    System.out.println("textReceived from" + e.getSource());
    String command = e.getText();
    if (LoggingBotConstant.LIST_PLACES.equals(command)) {
        // send a list a app names to client, sperated by comma.
        StringBuffer buffer = new StringBuffer();
        if (appNames != null) {
            for (int i = 0; i < appNames.size(); i++) {
                if (i != 0) {
                    buffer.append(",");
                }
                buffer.append(appNames.get(i));
            }
        }
        e.getIm().sendText(true, buffer.toString());
    }
} 





回页首


开发 Logging Bot Plugin

建立 Plugin 项目

新建一个 Plugin 项目 sametime.sample.client.loggingbot,该项目需要对如下几个扩展点进行扩展:

com.ibm.collaboration.realtime.messages.MessageHandlerListener : 用来监听 Sametime connect 平台所发出的消息。

org.eclipse.ui.views 和 com.ibm.rcp.ui.shelfViews : 主要用来建立 Logging Bot Plugin 的用户界面。Plugin 是以 Mini-Window 的形式运行在 Sametime Connect 中。

如果读者对如何使用这些扩展点不很熟悉,可以参考 RedBook<<Extending Sametime 7.5 Building Plug-ins for Sametime>>,参见 参考资源 部分。


图 3. Logging Bot Extensions
Logging Bot 扩展点

获取 STSession 对象

Logging Bot Plugin 运行在 Sametime Connect 中,但是它需要和 Logging Bot Server 进行交互,就需要使用 Sametime java toolkit,而其中的一个关键就是 STSession,所有的服务都是通过该 STSession 来获取。所以第一步必须在 Plugin 中获取该对象,Sametime Connect 在登录以后,会发出一个 ImConnectedMessage 的消息,可以对该消息进行如下处理从而获得 STSession 对象:


清单7:获得 STSession
                
public class LoggingBotMessageHandler extends DefaultMessageHandler{
... ...
public void handleMessage(ImConnectedMessage message) {
    session = getSTSession(message.getCommunityId());
    if(null == session){        
        return;
    }
    getPlaceList(session);
}
private STSession getSTSession(String communityID){       
    try {
        communityMgr = (CommunityService) ServiceHub
              .getService(CommunityService.SERVICE_TYPE);            
        RtcSession rs = communityMgr.getCommunity(communityID).getRtcSession();
        return (STSession) rs.getProtocolSession();
    } catch (ServiceException e) {
        e.printStackTrace();
    }
    return null;
}
... ...
} 

获取应用程序列表和建立用户界面

获得了 STSession 以后,Logging Bot Plugin 第一步要做的就是从 Server 获取应用程序列表,这可以通过向 Server 发送一个"LIST_PLACES" 的命令来实现。为了扩展性的考虑,Logging Bot Plugin 中对"命令"进行了抽象,以一个 Command 抽象类来表达。它接收一个 STSession 对象和一个文本的 command。在执行时,它通过 InstantMessagingService 向 Logging Bot Server 发送消息命令,接收到响应以后,把结果传送给抽象方法 handleResult()。

在 Logging Bot Plugin 中,GetPlaceListCommand 是 Command 抽象类的一个实现,它的功能就是发送 "LIST_PLACES"命令,拿到结果以后,在 handleResult() 方法中初始化 LoggingBotClientPlace 并且通知 LoggingBotView。


清单8:GetPlaceListCommand
                
public class GetPlaceListCommand extends Command {
    List places = new ArrayList();
    public GetPlaceListCommand(STSession session){
        super(session,LoggingBotConstant.LIST_PLACES);
    }
    public void handleResult(String result) {
        System.out.println("Place List from server:" + result);
        if(null == result){
            return;
        }
        String [] appNames = result.split(",");
        for(int i=0; i<appNames.length; i++){
            
            LoggingBotClientPlace place = new LoggingBotClientPlace(session,appNames[i]);
            this.places.add(place);
        }        
        LoggingBotView.INSTANCE.handlePlaceAdded(places);
    } 
}         
 

与 Server 端的 LoggingBotServerPlace 类似, LoggingBotClientPlace 用来表示客户端对"Place"的引用和操作,在初始化使用 PlaceService 来进入虚拟的 Place。注意:这些"Place"在 Logging Bot Server 启动的时候已经创建完成。 LoggingBotClientPlace 提供了两个重要的方法 enter() 和 left()。分别表示用户要订阅该 Place(也就是应用程序)的 Log 或者取消订阅。


清单9:Logging Bot Client Place
                          
public LoggingBotClientPlace(STSession session,String appName) {
    this.session = session;
    this.appName = appName;
    PlacesService placesService = (PlacesService) session
            .getCompApi(PlacesService.COMP_NAME);        
    place = placesService.createPlace(
            appName,
            appName, 
            EncLevel.ENC_LEVEL_DONT_CARE, 
            0);
    placeListener = new PlaceHandler();
    msgListener = new MyMsgHandler();        
}
public void enter() {    
    place.addPlaceListener(placeListener);
    place.enter();
}
public void left() {
    ... ...
    place.removePlaceListener(placeListener);
    place.leave(0);
}

到目前为止,所有后台的操作都已完成,Logging Bot Plugin 接下来需要创建用户界面,我们把UI相关的代码都放到 LoggingBotView 中来处理。该 View 提供的 createTab 方法会使用 SWT 的相关控件创建 图2 所示的界面,具体的细节请读者参阅 下载 部分的代码。


清单10:创建 UI
                
public void handlePlaceAdded(final List places) {
    this.comp.getDisplay().asyncExec(new Runnable() {
        public void run() {
            createTab(places);
        }
    });
}

接收 Log

在用户界面上,系统提供了"Subscribe"的选择,缺省情况下不订阅任何应用的 Log。如果用户在界面上如果选中了某个应用的"Subscribe"选项,系统会找到相关的 Place,判断是否已经进入到该 Place,然后简单的调用 enter() 方法即可。在 LoggingBotClientPlace 中,如果成功的进入该 Place,一个 MyMsgListener 就开始工作,监听从 Logging Bot Server 广播的消息。最后把接收到的 Log 写入到 LoggingBotView 中。


清单11:接收 Log
                
// 用户订阅了应用的Log
private void subscribeLog(String appName) {        
    LoggingBotClientPlace place = (LoggingBotClientPlace) this.places
            .get(appName);
    if (!place.isInPlace()) {
        System.out.println("Currently not in place, begin to enter");
        place.enter();
        return;
    } else {
        System.out.println("Already in place");
    }
}
// 接收到Log 以后,通知LoggingBotView进行更新
public class MyMsgHandler implements MyMsgListener {
        ... ...

        public void textReceived(MyselfEvent e) {
            System.out.println("Log received :" + e.getText());
            LoggingBotView.INSTANCE.handleLogReceived(appName,e.getText());
        }
}
//向Logging Bot View中添加Log
public void handleLogReceived(String appName, final String logMsg) {
    final Text txtLogMsg = (Text) this.logMsgTexts.get(appName);
    txtLogMsg.getDisplay().asyncExec(new Runnable() {
        public void run() {
            if (txtLogMsg.isDisposed()) {
                return;
            }
            if("".equals(txtLogMsg.getText())){
                txtLogMsg.setText(logMsg + "\r\n");
            }
            else{
                txtLogMsg.setText( txtLogMsg.getText() + logMsg + "\r\n");
            }                
        }
    });
}

同理,如果用户"退订"应用的 Log,系统会从该应用对应的 Place 中退出,把 MyMsgListener 删除。就不会接收到 Log 了。





回页首


总结

实时性是 sametime logging bot 最有价值的部分,如果应用程序发生错误,开发人员可以通过 logging bot 迅速的得知,快速的作出反应。同时,利用 Sametime 技术使得 Web 应用程序变的对开发人员非常友好,给开发,调试带来很大的乐趣。






回页首


下载

描述名字大小下载方法
Logging Bot PluginLoggingBotPlugin.zip1695KBHTTP
Logging Bot ServerLoggingBotServer.zip7KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术

讨论


作者简介

刘欣是一位IBM CSDL的软件工程师,目前从事企业电子商务应用的开发。您可以通过 liuxinlx@cn.ibm.com 和他联系。


常红平是一位IBM CSDL的软件工程师,目前从事企业电子商务应用的开发。您可以通过 changhp@cn.ibm.com 和他联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款