内容


在 IBM Lotus Quickr 中构建定制组件集成 IBM Lotus Sametime Unyte Meeting

Comments

编辑注:您很精通这个主题吗?希望分享您的经验吗?请马上加入到 IBM Lotus 软件 wiki 项目。

Lotus Quickr wikiLotus Sametime Unyte Meeting wiki

简介

为了解释本文的概念,开发了一个真实的、可部署的样例,它使 Lotus Quickr 能够与 IBM Lotus Sametime® Unyte® 集成,从而提供即时的在线会议功能。除了 Lotus Quickr 开发人员外,本文还可以令使用 Lotus Sametime Unyte API 进行开发的人员受益。但是,本文的重点是解释 Lotus Quickr 的组件开发步骤。如果希望向 Lotus Quickr 位置添加即时会议功能,那么可以使用本文提供的步骤说明进行部署。

本文针对的读者是具有 Java™、XML 和 portlet 开发知识的应用程序开发人员。了解基于 REST 的 API 将有助于理解本文使用的样例代码。

Lotus Quickr 是一款协作式软件,可以实现团队之间的内容共享。

Lotus Quickr 可以使用以下三个关键术语描述:内容共享,团队协作和连接器。

  • Lotus Quickr 是一个内容存储库,用户和团队在其中存储个人和团队内容。
  • 它允许实现团队协作,让用户能够存储、组织、访问和共享内容和团队项目。
  • 它为 Lotus Quickr 内容提供了用户界面。

其中一个用于协作用途的工具就是 Lotus Sametime Unyte Meeting。

Lotus Sametime Unyte 提供了集成的 Web、语音和视频会议服务,面向的客户从个人到小型企业和部门,再到大的跨国企业不等。Lotus Sametime Unyte 提供了应用程序编程接口(API),帮助供应商快速、简单地将 Lotus Sametime Unyte 与其他现有产品和服务以及其他 IBM 产品(IBM WebSphere Portal 和 Lotus Quickr)集成在一起。基于 URI 的 API 允许公司将 Lotus Sametime Unyte 插入到他们的应用程序中。

本文描述了如何将 Lotus Sametime Unyte Meeting 集成到 Lotus Quickr 8.1.1。本文后面的小节描述并分析了一个样例 Lotus Sametime Unyte Meeting Java Platform, Enterprise Edition Lotus Quickr 组件,该组件支持这种集成。但是,我们将首先大体介绍组件构建及其在 Lotus Quickr 中的作用。

Lotus Quickr 中的组件

在这一小节中,我们描述了构建可以部署到 Lotus Quickr 的典型组件所需采取的步骤。顾名思义,WebSphere Portal 的 Lotus Quickr 服务构建在 Websphere Portal 之上。特别是,它使用了 WebSphere Portal 提供的复合应用程序基础设施(CAI)。

CAI 定义了可以开发新组件以增强 Lotus Quickr 功能的接口。这一主题超出了本文的讨论范围,涉及到整个框架的细节。(参见本文参考资料部分的相关资料,获得有关框架的更多细节)。我们主要关注构建组件所需的接口。您也可以参考以下位置提供的有关这些接口的文档:

<Quickr Install Location>/PortalServer/doc/Javadoc/spi_docs

Lotus Quickr 组件在 Lotus Quickr 位置的内部使用。组件可以和 portlet 一样简单。更复杂的组件可以使用它自己的库来存储内容、定义定制角色(用来定制功能)并执行备份和恢复操作。

构建组件

构建组件需遵循以下这些步骤:

  1. 开发提供了组件的用户界面和其他特性的 portlet。
  2. 通过实现合适的 CAI 接口开发组件处理程序。
  3. 提供一个 plugin.xml 文件,其中指定了组件处理程序(通常位于包含组件实现的 JAR 文件的根部,该文件通常被打包在 WAR 文件中)。
  4. 使用 portlet.xml 绑定 portlet 和组件处理程序。

应当注意,组件处理程序可以打包到 portlet WAR 文件的内部,也可以打包到一个独立的 JAR 文件(可以从类路径获得)。在服务器启动时,CAI 尝试构建一个保存所有已部署组件的存储库。具体做法是加载类路径中可见的所有 plugin.xml 文件。组件的 plugin.xml 文件应当提供对 CAI 业务组件的扩展点,即 com_ibm_portal_app.BusinessComponents。

清单 1 提供了一个例子。

清单 1. 样例组件插件声明
 <?xml version="1.0" encoding="UTF-8"?> 
 <plugin id="sample.plugin" version="1.0.0"> 
   <extension 
       point="com_ibm_portal_app.BusinessComponents"
       id="MyFirstComponent"> 
        <provider class="sample.MyFirstHandler"> 
        </provider> 
   </extension> 
 </plugin>

注意:确保版本号包含了三个部分。

在 plugin.xml 文件中指定的 provider 类中,必须实现组件参与所需的所有接口。CAI 定义了组件可以参与到其中的一组接口;它们分别为:

  • 生命周期(Lifecycle)。该接口定义了组件可以实现的必要回调,这样就可以侦听诸如组件实例的创建和删除等事件。当发生这些事件时,CAI 将尝试在这个接口中调用相应的方法。
  • 成员关系(Membership)。成员关系接口定义了回调方法来监听与访问控制有关的事件,比如添加或删除一个或多个用户对组件的访问。当发生这类事件时,组件可以使用这个接口执行额外的操作。
  • 序列化(Serializable)。组件实现这个接口来参与多个位置的备份和恢复操作。当一个 Lotus Quickr 位置完成备份后,每个组件将收到通知,要求备份其自身的内容。如果组件并不管理自身的内容,那么就不需要实现该接口。
  • 模板化(Templatable)。任何 Lotus Quickr 位置都可以保存为一个模板。这个接口允许您使用相同的一组组件快速创建一个新的位置。当将一个位置保存为模板后,CAI 将使用这个接口通知该位置的每个组件。通过实现这个接口,组件可以将状态保存到模板中,这样就可以在从模板中创建新实例时检索状态。

并非所有这些接口都是强制的。如前所述,可以构建一个简单的 portlet 来提供功能并在一个 Lotus Quickr 位置的内部使用它。为一个或多个这类接口提供实现正是 portlet 不同于组件的地方。您可以根据需求衡量是否需要实现这些接口。

最后,构建好 portlet、组件处理程序和 plugin.xml 文件之后,应当更新 portlet.xml 文件以将 portlet 绑定到组件。通过执行这一步骤,允许 CAI 将这个 portlet 作为一个业务组件对待,并开始对组件处理程序调用相应的方法。要执行绑定,您只需要将清单 2 所示的优选配置添加到 portlet.xml 文件。

清单 2. 将组件处理程序和 portlet 绑定在一起的 Portlet 优选配置
 <portlet-preferences> 
  <preference> 
    <name>com.ibm.portal.bc.ref</name> 
    <value>portal:extreg/sample.plugin.MyFirstComponent</value> 
  </preference> 
 </portlet-preferences>

优选配置的值分为两个部分。首先,它使用了前缀 portal:extreg/。这是所有业务组件引用的 JNDI 前缀。第二个部分实际上组合了插件 ID(sample.plugin)和 plugin.xml 文件提供的扩展点 ID(MyFirstComponent)。

样例:Sametime Unyte Meeting 组件

这个样例详细解释了如何构建定制组件并应用于 Lotus Quickr 位置。用于演示这个组件的样例是一个 Lotus Sametime Unyte 会议 portlet,它允许一个位置的成员使用他们的个人 Lotus Sametime Unyte 帐户发起 Lotus Sametime Unyte 会议并允许其他团队成员加入到会议中。组件使用 portlet 作为 CAI(Composite Application Infrastructure,位置的组件运行时)业务组件的前端。这个业务组件使用 Lotus Sametime Unyte Meeting Server API 访问 Lotus Sametime Unyte 服务。

构建的样例 portlet 允许位置成员访问 Lotus Sametime Unyte 会议。位置成员可以通过在图 1 所示的简单表单中输入他们的个人 Lotus Sametime Unyte 帐户细节来发起一次会议。

图 1. 尚未开始会话的会议的 Portlet 视图
尚未开始会话的会议的 Portlet 视图

单击 Host Meeting 按钮后,将在一个新的浏览器窗口并在 presenter 模式下打开 Lotus Sametime Unyte Web Conference UI。现在,当位置的其他成员导航到 portlet,他们将看到一个视图,其中显示了当前会议会话的细节以及一个允许他们加入到会议中的按钮,如图 2 所示。

图 2. 开始会话后的会议的 Portlet 视图
开始会话后的会议的 Portlet 视图
开始会话后的会议的 Portlet 视图

结束一次会议后,将显示一个视图,展示上次会议的细节和用来发起新会议会话的表单。最后,portlet 支持一种 Edit Settings 模式,该模式给出一个简单表单,允许用户修改组件所使用的会议服务配置。

组件交互

图 3 展示了如何构造和交互该样例的各个组件。

图 3. 组件交互
组件交互
组件交互

这些组件的构造和交互方式将在后续小节中详述,首先介绍 Lotus Sametime Unyte Meeting 服务器,然后讨论充当业务组件视图的会议 portlet,最后讨论业务组件。

部署样例

可以从本文的样例代码中找到组件的预构建副本。在 Deployment 文件夹中,查找 unyte.meetings.war 文件。这个文件应当复制到 Lotus Quickr 部署的 installableApps 文件夹中:

<quickr>/PortalServer/installableApps

样例通过附带的 xmlaccess 脚本完成部署。Xmlaccess 是一个脚本化接口,提供了对 WebSphere PortalServer 配置命令的访问。要为 portlet 分配惟一的名称(需要用来将组件包含到 Lotus Quickr 组件面板中),需要使用 xmlaccess 部署组件。(如果组件不会被包含到面板中,那么可以使用 Administration 部分的 Lotus Quickr Web 模块部署 UI)。xmlaccess 脚本还位于本文下载部分提供的代码的 Deployment 文件夹中。这个脚本非常简单;它将部署 Web 模块,然后为 portlet 分配一个惟一的名称。有关 xmlaccess 的更多信息,请访问 Information 中心。在 <quickr>/PortalServer/doc/xml-samples 文件夹中还可以找到大量脚本样例。

要运行脚本,首先必须将其复制到服务器中。然后,放到 <quickr>/PortalServer/bin 文件夹中。提供指定管理员用户和密码的参数并指向部署脚本,xmlaccess 脚本(xmlaccess.bat / xmlaccess.sh)随后就可以运行了:

xmlaccess – url <configurl> – user <adminuser> -password <adminpwd> -in /temp/deployUnyteSample.xml

其中,adminuser 是管理员用户的 ID,adminpwd 是相应的密码,in 参数指向部署脚本,而 url 参数表示 Portal config URL(通常为 http://<host>:10038/lotus/config)。

完成组件部署后,如果希望立即开始使用它,那么可以往前跳到有关将组件添加到面板的小节。注意这一步骤是可选的;如果没有在面板中包含组件,那么仍然可以通过将 portlet(Sametime Unyte Meeting)放到位置中来完成添加。

部署完组件后,需要重启服务器,因为组件包含了一个服务器扩展。这些扩展将在服务器重启时加载到扩展库中。

Lotus Sametime Unyte Meeting Server API

Lotus Sametime Unyte 提供了一个 Web 配置管理 API,可供合作伙伴和用户用于将 Web 会议功能集成到他们的应用程序中。这个 API 基于 HTTP,并且通常通过一个使用 SSL 保护的连接来进行操作。API 调用的方式为:将 API 请求以 XML 数据的形式通过 HTTP 发送给指定的单一入口点 URL。GET 和 POST 方法都可以使用,每种方法都可以使用相同的功能(只是请求格式稍有不同)。这个实例使用了 GET 方法发送请求,但是可以很轻松地修改为使用 POST 方法。为了保持简单,这个样例并没有使用 SSL;但是,要用于生产系统,可能需要使用 SSL。

Lotus Sametime Unyte Meeting Server API 提供了对表 1 中列出的服务的访问。

表 1. Lotus Sametime Unyte Meeting Server API 提供的服务
服务功能
会话管理开始会议(包括离线模式和演示模式);加入会议;从会议中移除与会者;获得当前或历史会议会话的细节;修改当前展开会话的会议的属性
供给管理对服务供给工件(比如客户、订阅者和端口群)的管理(创建、删除和更新)
内容管理在服务器上发布和删除演示;在当前展开会话的会议中开始一个演示;停止当前演示;从服务器检索演示;检索会话记录
会议管理回调可以用于将一组特定于应用程序的处理插入到服务事件(比如会议会话开始和结束、加入和离开会议)的一组回调

对于这些 API 内容,本样例只使用了来自会话管理部分的调用。具体来讲,使用了可生成 URL 的 API(URL 可以用来开始和加入会议会话),以及可获得特定会话的细节的 API。

API 调用的形式

API 调用采用对已有服务入口点 URL 发出 HTTP 请求的形式,并对请求细节采用 XML 数据形式。对于这个样例,我们使用 GET 形式的请求;URL 模式为:

http://<service_entry_point>/register/api/main.asp?xml=<xmlstring>

其中,<xmlstring> 以 XML 形式包含请求细节。XML 的形式取决于要使用的具体 API。要使用 API 开始会议会话,需要使用清单 3 所示的 XML。

清单 3. Start Meeting 请求 XML 的格式
 <WDAPI ver="1.1" type="call" name="startSession" utc="utc_milliseconds"> 
	 <PARAMETERS> 
		 <SUBSCRIBER subscriber_parameters_list /> 
		 <VAPI vapi_parameters_list /> 
		 <SESSION session_parameters_list /> 
		 <PARTICIPANT participant_parameters_list /> 
		 <BRAND brand_parameters_list> 
			 brand_override_elements_list 
		 </BRAND> 
	 </PARAMETERS> 
	 <CHECK check_parameter_list /> 
 </WDAPI>

其中,名称指定了被请求的 API 方法,而 utc_milliseconds 是以毫秒为单位的 UTC 时间(在 Java 中,这等同于调用 System.currentTimeMillis())。其余 XML 主要包含两个部分。PARAMETERS 部分包含请求的所有细节。这里例子中并没有使用所有的细节,比如 VAPI 部分,其中包含了语音会议细节,而 SESSION 部分允许为会议会话提供额外的选项。对于本例,比较重要的部分为 SUBSCRIBER 和 PARTICIPANT,前者包含了服务细节并确认了会议发起者的身份,而后者定义了如何在 Lotus Sametime Unyte Meetings Web UI 中将发起人细节显示给其他与会者。

包装基于 HTTP 的 Meeting Server API 的 Java 包装器

由于该组件是一个基于 Java 的组件,您将创建一个 Java 包装器来封装基于 HTTP 的 Meeting Server API,以用于您的组件。这个包装器隐藏了实现细节,比如构建 API 请求 XML 以及从组件其余部分生成用于身份验证的安全散列(后面将详述)。作为该包装器的一部分,还将创建类来表示 API 请求的数据部分(订阅者、商标等等),从而为需要发送 Meeting Server API 请求的客户机代码提供方便。

这些包装器类位于 com.ibm.quickr.meetings.unyte.service 包。首先,查看 SametimeUnyteService 类,它包含方法 getStartSessionURL(...)。这个方法聚集了用于 Meeting Server API 调用的请求 XML,以在 Lotus Sametime Unyte 中开始一个新的 Web 会议会话。这个方法将导致在 presenter 模式下在一个新的浏览器窗口中显示 Lotus Sametime Unyte conferencing UI。注意,如何使用一组包装器对象来将参数传递给 getStartSessionUrl( … ) 方法。XML 请求将根据这些参数构建。有关更多细节,请参考附带的源代码中的注释。

在单个参数部分中生成 XML 的任务被委托给一组类执行,这些类将针对特定 API 方法构建 XML,检验是否给出了该方法的所有必需参数,并生成 MD5 散列作为 API 安全模型的一部分(参加后面的小节)。例如,Subscriber 类封装了构成部分请求的订阅者数据(例如,一个开始会话的请求)。它针对所有 participant 参数包含了 getter 和 setter 方法,并且包含了验证逻辑来确保给出了针对特定方法调用的所有必需参数。getRequestXml(...) 方法为将被包含在请求的 PARAMETERS 元素的会话生成 XML 数据,而 generateCheck() 方法生成将被包含在 CHECK 元素的散列值。

SametimeUnyteService 类提供了方法来处理生成的请求 URL(开始或加入会议会话),并且提供一种方法确定某个会话的状态。有关此内容的更多信息,以及作为 Java 包装器(封装 Meeting Server API)的一部分提供的完整对象集合,请参考本文下载部分提供的样例代码,它给出了详尽的解释。

验证 Meeting Server API 请求

Meeting Server API 使用了一个安全散列程序来验证请求,并对每个请求的生命周期施加了一个 10 分钟的时间限制,这个时间限制从生成请求的那一刻开始计算(这个散列程序中包含了一个时间戳)。查看一个开始会话请求的 XML 的通用格式,如清单 4 所示(突出显示的部分)。

清单 4. Start Sesssion 请求 XML 的通用格式
 <WDAPI ver="1.1" type="call" name="startSession" utc="utc_milliseconds"> 
  <PARAMETERS> 
    <SUBSCRIBER subscriber_parameters_list /> 
    <VAPI vapi_parameters_list /> 
    <SESSION session_parameters_list /> 
    <PARTICIPANT participant_parameters_list /> 
    <BRAND brand_parameters_list>brand_override_elements_list</BRAND> 
  </PARAMETERS> 
  <CHECK check_parameter_list /> 
 </WDAPI>

utc 属性是一个时间戳,表示生成 API 请求的时间。Check 部分包含执行散列后的 XML 的请求参数部分,使用清单 5 所示的格式。

清单 5. 请求 XML 的 Check 部分
 <CHECK subscriber="2b0694d2660ab919b6a2d19a061884d3"
	 vapi="2b9694d22667a9acc6a2d19a068884d4"
	 session="2b0694d2660ab919b6a2d19a06188a99"
	 participant="2b9694d2260ab9acc6a2d19a061884d6"
	 brand="2b0694d2660ab919b6a2d19a06188b97"/>

每个 check 属性组成了以下连接字符串的 MD5 散列(一种通常用于生成加密散列的消息摘要算法):

<private_token> + <utc_time> + <element_string>

其中 private_token 是一个秘密令牌,该令牌只对服务提供商和受信任的 API 客户机是已知的,utc_time 表示请求生成的时间(与 WDAPI 元素中的 utc 属性相同),而元素字符串是相应元素的完整内容。128 位散列结果被表示为 32 个十六进位数字组成的字符串。

这个散列程序被实现为 Meeting Server API 的 Java 包装器的一部分,Meeting Server API 被作为样例的一部分提供。有关实现的更多信息,请参见 SametimeUnyteService 类(聚集请求 XML)和 generateCheck(...) 方法(在每一个特定的参数处理类中实现,比如 Subscriber、Session 等等)。

业务组件实现

生命周期接口

业务组件实现类为 MeetingsBCHandler,位于 com.ibm.quickr.meetings.unyte.bc 包。这个类通过实现 CAI Component SPI 的接口,使用 CAI 架构履行业务组件契约。在这些接口当中,对于本文样例最重要的接口就是生命周期接口。您需要这个组件的实例来维护状态(跟踪当前会议会话 ID 或上次会议描述等内容项),而 WebSphere Portal V6.0.1(Lotus Quickr 8.11 的基础)中的 CAI 基础设施并没有提供组件实例级别的属性维护支持。同样,单个组件实现需要为与组件实例相关的数据实现自己的存储机制。通常,这个实现必须存储在数据库或一个 JCR 文档中,但是为了保持简单,本文的样例组件使用文件系统中的一个文件存储维护组件状态所需的名称 —— 值对集合。每个组件实例使用一个独立的关联文件存储其状态,这个独立的文件将在组件实例初始化期间创建,并在组件销毁时被清空(即删除)。要完成这个任务,需要在生命周期接口中实现方法。

在创建业务组件的新实例时,将对该业务组件调用 createInstance(...) 方法。当将一个被作为该业务组件的视图关联的 portlet 添加到某个位置时,通常会出现这种调用(参见下文中介绍 portlet 和业务组件之间的关系的小节)。清单 6 展示了本样例中的 createInstance(...) 方法。

清单 6. 组件处理程序的 createInstance( … ) 方法
 public ListModel createInstance(ListModel parameters) throws ComponentException { 
  try { 
    String timestamp = Long.toString(System.currentTimeMillis()); 
    String bcId = "unyte_meeting_" + timestamp; 
        
    ConfigFileHelper configHelper = ConfigFileHelper.createConfigFile(bcId); 
    configHelper.setConfigParam("teamspace.meetings.bc.svcUrl", 
    "http:/conferenceserver.renovations.com/register/api/main.asp"); 
    configHelper.setConfigParam("teamspace.meetings.bc.svcProv", "SVC_PROV"); 
    configHelper.setConfigParam("teamspace.meetings.bc.billingType", "t"); 
    configHelper.setConfigParam("teamspace.meetings.bc.validationCode", "XXXX"); 
    configHelper.setConfigParam("teamspace.meetings.bc.brandName", "RENOVATIONS"); 
    configHelper.setConfigParam("teamspace.meetings.bc.meetingDesc", "----"); 
        
    ArrayList list = new ArrayList(); 
    list.add(new VariableImpl("id", "id", "the id", bcId)); 
        
    return ListModelHelper.from(list); 
  } 
  catch(MeetingServiceException mse) { 
    throw new ComponentException(mse); 
      } 
 }

这里的 createInstance(...) 方法执行了两个重要的操作。首先,它创建了一个用于存储实例状态的配置存储文件。针对此目的提供了一个 helper 类 ConfigFileHelper(位于同一个 com.ibm.quickr.meetings.unyte.bc 包中)。它支持在文件系统中创建和删除配置文件,并在这些文件内查询和设置单独的属性(用于存储状态信息)。本文不会深入研究这个实现;它并不重要。有关更多细节,参考本文附带的样例代码中的类。

对于组件实例的创建,您需要填充一些初始的配置细节。考虑到本样例的目的,我们为每个组件实例包含了一个服务器配置副本,并通过 portlet 的 edit 设置进行访问(下文详细介绍了这个定制模式)。在 createInstance(...) 方法中,您需要为配置填充一些默认值。

其次,createInstance(...) 方法的职责是在创建时为每个组件实例分配一个单独的标识符;这个 ID 对于该组件类型的所有实例必须是惟一的。此 ID 是必须从方法中返回的 ListModel 的必需列表元素,它用于识别后续 CAI 交互中的组件实例。

removeInstance(...) 方法在组件实例被销毁后得到调用,当链接到组件的 portlet 被从某个位置中移除,或者这个位置本身被移除后,那么就将发生组件实例销毁。removeInstance(...) 方法允许组件实例执行定制的清空操作。注意,这个方法采用异步调用;在服务器上将每天运行一次组件 cleanup 守护进程,该守护进程对自上一次运行后被删除的任何组件实例调用该方法。这个过程还在服务器启动时运行。对于样例组件,需要将与被删除组件相关的配置文件从文件系统中删除,如清单 7 所示。

清单 7. 组件处理程序的 removeInstance( … ) 方法
 public void removeInstance(String bcId) throws ComponentException { 
    try { 
        ConfigFileHelper.deleteConfigFile(bcId); 
    } 
    catch(MeetingServiceException mse) { 
        throw new ComponentException(mse); 
    } 
 }

模板化接口

模板化接口将组件状态的序列化移入到模板,或从其中移出。模板指的是包含应用程序(位置)创建计划的 XML 文档,这个创建计划中包含了有关所有内含组件及其相互关系的细节。

当将某个位置保存为模板时,serializeToTemplate(...) 方法允许位置中的组件记录其状态,以便包含到模板 XML 中。本文的样例并未包含任何定制序列化处理(但是可以对其进行修改,比如,序列化组件状态中的会议服务配置内容)。

从包含组件的模板中创建一个新位置后,将生成一个新的组件实例,此时将调用 createFromTemplate(...) 方法。该方法被传递到在 serializeToTemplate(...) 方法期间被序列化到模板的数据中。尽管未对数据执行序列化,但是当作为模板的一部分创建实例时,样例必须实现这个方法来涵盖组件初始化。这与 Lifecycle.createInstance(...) 方法中执行的初始化是相同的;必须创建一个新的配置文档来存储组件实例的状态。和 Lifecycle.createInstance(...) 方法一样,Templatable.createFromTemplate(...) 方法必须为新的组件实例生成并返回一个惟一的标识符。

其他 CAI 组件 SPI 接口

这个组件并未实现任何其他组件接口(如上所示,只需要生命周期接口和模板化接口)。使用其他 CAI 组件接口是一个可选的选项。需要公开组件角色并根据这些角色管理资源访问的组件需要实现成员关系接口,但是,如果没有必要的话,则可以忽略这个接口。有关 CAI 组件接口的更多信息,请参见位于 <quickr>/PortalServer/doc/Javadoc/spi_docs 的接口 Javadoc。

特定于组件的业务组件方法

除了实现所需的 CAI 组件 SPI 接口外,业务组件通常还公开了一组方法来支持组件的业务逻辑。在本样例中,这些方法与管理 Lotus Sametime Unyte 会议有关:开始、加入和获得 Web 会议会话的状态。这些方法由提供业务对象前端的 portlet 使用。MeetingsBCHandler 类包含了这些方法;它们在 CAI Component SPI 接口实现之后被组合在一起。有关这些方法的功能的更多信息,请参见附带的源代码中的注释。

使用框架注册组件

CAI 业务组件使用了一个由 WebSphere Application Server 扩展库实现的插件架构,用于管理服务器扩展。要部署业务对象,需要使用插件描述符将实现类放到系统类路径中。由于目前将业务组件引入到应用程序中的惟一方法是包含一个链接到业务组件的 portlet(作为业务对象的视图;参见下面的小节),因此将业务组件二进制文件和插件描述符连同 portlet 打包到一个 WAR 文件中并部署到服务器上。然后存储卡就可以选择扩展,并且业务组件实现可以用于相同的范围。

要为我们的样例实现注册,需要将插件描述符打包到包含业务组件(和 portlet)类中的 JAR 中。参见附带的样例代码中的 plugin.xml 和清单 8 所示的代码。

清单 8. 样例组件的插件声明
 <?xml version="1.0" encoding="UTF-8"?> 
 <plugin id="com.ibm.quickr.meetings.unyte"version="8.1.1"> 
  <extension point="com_ibm_portal_app.BusinessComponents" id="UnyteMeetingsBC"> 
    <provider class="com.ibm.quickr.meetings.unyte.bc.MeetingsBCHandler">
    </provider> 
  </extension> 
 </plugin>

特别要注意加粗显示的代码。所有 CAI 业务组件都需要与之关联的扩展点是 com_ibm_portal_app.BusinessComponents。每个扩展也需要一个 ID,包含两部分组成,插件 ID 和扩展 ID。最后,业务组件实现类被指定为扩展提供者。

将业务组件与 portlet 关联起来

通过将业务组件与 portlet 链接起来,可以将它们引入到位置当中。portlet 随后可以充当此业务组件上的一个视图。要形成这种关联关系,需要使用 portlet 优选配置来提供对将被关联的业务组件的引用。优选配置的名称为 com.ibm.portal.bc.ref,它在 portlet.xml 文件中被指定,如清单 9 所示。

清单 9. 样例组件的 Portlet 描述符
 <?xml version="1.0" encoding="UTF-8"?> 
 <portlet-app id="UnyteMeetingsPortletApp1"
    xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
    version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd 
    http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"> 
    <portlet> 
        <portlet-name>SametimeUnyteMeetingsPortlet</portlet-name> 
        <display-name>Sametime Unyte meetings portlet</display-name> 
        <portlet-class> 
            com.ibm.quickr.meetings.unyte.portlet.SametimeUnyteMeetingsPortlet 
            </portlet-class> 
        <expiration-cache>0</expiration-cache> 
        <supports> 
            <mime-type>text/html</mime-type> 
            <portlet-mode>view</portlet-mode> 
            <portlet-mode>edit_defaults</portlet-mode> 
        </supports> 
        <supported-locale>en</supported-locale> 
        <portlet-info> 
            <title>Sametime Unyte meeting</title> 
        </portlet-info> 
        <portlet-preferences> 
            <preference> 
                <name>com.ibm.portal.bc.ref</name> 
                <value>portal:extreg/com.ibm.quickr.meetings.unyte.UnyteMeetingsBC 
                </value> 
            </preference> 
        </portlet-preferences> 
    </portlet> 
    <custom-portlet-mode> 
        <portlet-mode>edit_defaults</portlet-mode> 
    </custom-portlet-mode> 
 </portlet-app>

当将一个 portlet 实例引入到位置中后,这个优选配置将由 CAI 运行时进行检验,并且它知道如何实例化一个新业务组件实例并将其与 portlet 关联起来。注意组件标识符的格式,其中包含有 portal:extreg/ 前缀(表示可以从组件库中加载组件)和组件 ID,组件 ID 包含插件 ID(本例中为 com.ibm.quickr.meetings.unyte)和扩展 ID(UnyteMeetingsBC),之间用点号分隔。

样例会议 portlet

portlet 的结构

本样例中包含的 portlet 是一个简单的 JSR 168 portlet。它支持两个模式,一个主视图模式,用于呈现 UI 以显示会议状态并允许用户开始和加入会议;一个编辑设置模式,允许用户修改组件配置的各个部分(服务提供者细节)。com.ibm.quickr.meetings.unyte.portlet 包中的 SametimeUnyteMeetingsPortlet 类是 portlet 实现类。

如果打开所包含的工作空间,可以看到如图 4 所示的 portlet 资源的布局(对图 4 中的两个方框使用了边线):

图 4. 突出显示 portlet 资源的组件布局
突出显示 portlet 资源的组件布局
突出显示 portlet 资源的组件布局

呈现主 portlet 视图

视图 JSP(mainView.jsp)由一个会话 bean 传递给会话状态,并相应地呈现 UI。需要注意的一点是刚刚开始或加入会议时的行为。在这些情况下,包含了 JavaScript 来打开 Meeting Server API URL(针对 startSession 或 startParticpiant 调用)并随后设置一个计时器,超过这个计时后,会议应当结束初始化并触发页面重载。JSP 的其余部分则非常标准;为了保持简单,通过内联的方式包含样式和 JavaScript,但是将使用更加典型的方法具体化这些内容。有关更多信息,请参考附带的样例代码中的 JSP 源文件。

支持定制的编辑设置模式

Lotus Quickr 使用定制的 portlet 模式支持编辑设置操作,此操作可以在 Lotus Quickr portlet 皮肤中使用。样例 portlet 使用此模式给出一个表单,允许用户编辑组件实例的会议服务配置(比如服务主机细节)并将其保存回组件。要支持这种定制模式(由 Lotus Quickr 使用),首先必须将模式声明为受 portlet 支持的定制模式;要执行这一步,需要将模式的引用包含到 portlet.xml 中,如清单 10 所示。

清单 10. 为 portlet 描述符中的 edit_defaults 模式添加支持
 <?xml version="1.0" encoding="UTF-8"?> 
 <portlet-app id="UnyteMeetingsPortletApp1"
    xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
    version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd 
    http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"> 
    <portlet> 
        <portlet-name>SametimeUnyteMeetingsPortlet</portlet-name> 
        <display-name>Sametime Unyte meetings portlet</display-name> 
        <portlet-class> 
            com.ibm.quickr.meetings.unyte.portlet.SametimeUnyteMeetingsPortlet 
            </portlet-class> 
        <expiration-cache>0</expiration-cache> 
        <supports> 
            <mime-type>text/html</mime-type> 
            <portlet-mode>view</portlet-mode> 
            <portlet-mode>edit_defaults</portlet-mode> 
        </supports> 
        <supported-locale>en</supported-locale> 
        <portlet-info> 
            <title>Sametime Unyte meeting</title> 
        </portlet-info> 
        <portlet-preferences> 
            <preference> 
                <name>com.ibm.portal.bc.ref</name> 
                <value>portal:extreg/com.ibm.quickr.meetings.unyte.UnyteMeetingsBC 
                </value> 
            </preference> 
        </portlet-preferences> 
    </portlet> 
    <custom-portlet-mode> 
        <portlet-mode>edit_defaults</portlet-mode> 
    </custom-portlet-mode> 
 </portlet-app>

然后,portlet 实现类必须将这个定制定义为一个 portlet 模式,并检查它是否是 doDispatch(...) 方法(该方法必须被重写)的一部分,如清单 11 所示。

清单 11. 定义 portlet 模式
 public static final PortletMode EDIT_DEFAULTS_MODE = 
 	 new PortletMode("edit_defaults"); 

 protected void doDispatch(RenderRequest request, RenderResponse response) 
	 throws PortletException, java.io.IOException { 
    WindowState state = request.getWindowState(); 
    if (!state.equals(WindowState.MINIMIZED)) { 
        PortletMode mode = request.getPortletMode(); 
        if (mode.equals(PortletMode.VIEW)) { 
            doView(request,response); 
        } 
        else if (mode.equals(PortletMode.HELP)) { 
            doHelp(request, response); 
        } 
        else if (mode.equals(EDIT_DEFAULTS_MODE)) { 
            doEditDefaults(request,response); 
        } 
    } 
 }

最后,doEditDefaults(...) 方法将请求发送给一个 JSP(editConfig.jsp),这将给出一个表单,允许用户为会议服务配置输入新值。

初始化会话 bean

portlet 使用一个会话 bean 来维护和传递 portlet 和组件状态。这个 bean 使用一些基本信息完成一次初始化(在一次会话内首次访问 portlet 时),这些信息对于 portlet 会话保持不变。这个初始化发生在 initializeSessionBean(...) 方法内部。有关当前用户和业务组件的信息将被记录。portlet 需要知道有关业务组件的两点内容。首先,它需要获得组件的一个句柄,这样就可以通过读取 portlet 优选配置(标识相关业务组件)来访问其业务方法(参见上面的小节)。参见清单 12。

清单 12. 通过使用 portlet 优选配置获得组件处理程序
 private MeetingsBCHandler getBC(PortletRequest request) { 
   try { 
    Context ctx = new InitialContext(); 
    Object bc = ctx.lookup(request.getPreferences().getValue 
    ("com.ibm.portal.bc.ref", "")); 
    return (MeetingsBCHandler)bc; 
  } 
    catch (NamingException e) { 
    e.printStackTrace(); 
    return null; 
  } 
 }

由于 JNDI 查找属于开销较大的操作,因此对 BC 的引用只执行一次查找,然后将其存储起来以供会话的其余部分使用。为了保持简单,我们查找了组件处理程序并将其转换为已知的实现类类型。一种更典型的方法是使用单独的接口指定组件的业务方法,客户机代码可通过业务方法访问组件。

对于 CAI 业务组件,不会针对每个组件实例创建 handler 类的对象;相反,组件处理程序中的每个方法都需要将组件实例 ID 作为必需的参数。鉴于这个原因,portlet 要访问组件,它需要判断与这个特定 portlet 实例相关的业务组件实例的 ID。判断是通过读取另一个 portlet 优选配置完成的,优选配置被作为组件初始化的一部分由基础设施编写。这个优选配置为 com.ibm.portal.bc.instance.id。它被设置为组件在 createInstance(...) 或 createFromTemplate(...) 方法期间生成的 ID 值(取决于创建组件实例的原因,是将 portlet 添加到位置,还是模板被实例化):

String bcId = request.getPreferences().getValue("com.ibm.portal.bc.instance.id", "");
sessionBean.setAttribute("bcId", bcId);

portlet 中的操作

portlet 会发生三个可能的操作,这些操作像往常一样在 portlet 实现类的 processAction(...) 方法中处理。第一个操作是发起会话会议,当用户填写 subscriber details 表单并单击 Start Meeting 按钮时将发生这个操作。在本例中,表单的值在会话 bean 和组件状态中读取和记录,并将记录一个时间戳(根据它设置会话初始化超时设置),并且修改状态来反应会议是否已经开始(参见下文中有关状态变量的小节)。第二个操作发生在用户加入会议时。在本例中,需要记录一个时间戳,根据它设置会议 UI 的初始化超时。最后,第三个操作可以在 portlet 处于编辑设置模式时确定服务配置选项是否已被修改(参见下文)。在本例中,新的会议服务配置被记录到组件中,视图模式被设置会 view。有关更多细节,请参考 processAction(...) 方法实现。

确定会议会话状态

portlet 使用大量状态变量控制执行流程并判断将呈现哪个 UI。这些状态变量由业务组件集中存储(因为需要跨多个属于不同用户的 portlet 会话共享状态)并从其中读取到会话 bean,会话 bean 随后被传递给 portlet(例如,UI 使用这个会话 bean 确定呈现内容)。这些状态变量如表 2 所示。

表 2. 状态变量
状态变量功能
viewState这个变量记录会议会话的状态,并用于判断要为 portlet 视图呈现的内容。这里包含多个可能的状态,包括:活动(会议会话是活跃的);非活动(会议会话当前不是活跃的,但是此前发起过会话);初次会话(会议会话当前不是活跃的,并且该组件此前没有发生过任何会议);等待会议开始(会议会话已经被启动,但是 Web 会议尚未结束初始化,因此会话 ID 是未知的,并且无法检索到任何会话细节);等待加入会议(当前用户正在等待 Web 会议 UI 初始化,以便参与到会议中)。
lastSessionId与此组件相关的最近一次会议的会话 ID。当会话处于非活动状态时,该 ID 用于显示最近一次会议的细节。
lastSessionSubscriberId与此组件相关的最近一次会议的订阅者 ID(结合了最近一次会议的会话 ID,用来检索最近一次会议会话的细节,如果需要显示的话)。
lastMeetingDesc最近一次会议会话的描述。
currentSessionId处于活动状态时的当前会话的会话 ID。
currentSessionSubscriberId会话处于活动状态时,用于开始当前会话的订阅者 ID。
meetingDesc会话处于活动状态时的当前会议的描述。
lastStartActionTime记录了最近一次会议的发起时间。该记录是必需的,因为 portlet 需要允许一定的时间来等待会议会话的初始化。超过这个时间之后,如果会话仍然不存在,那么将假设初始化失败或被中断;组件随后返回到一个非活动状态。
lastJoinActionTime记录当前用户加入到当前的活动会议会话的时间。这个记录用于管理 UI,此时用户将等待会议会话在 participant 模式下启动。将设置一个超时,超过这个时间后,会议会话初始化将被认为失败或被中断,而 UI 则改为显示当前用户未参与的会话活动(即显示 Join 按钮)。

作为视图呈现的一部分,getSessionInfo(...) 方法在 portlet 实现类中得到调用;此时将更新状态(并记录回组件和会话 bean)。查看 getSessionInfo(...) 方法了解状态判断逻辑的实现细节。请求随后被发送到视图 JSP(mainView.jsp),以根据当前会话状态呈现 UI。

将组件添加到 Lotus Quickr 组件面板

创建新的 Lotus Quickr 组件的最后一步是将它添加到组件面板中,后者将在定制位置时显示,如图 5 所示。

图 5. Lotus Quickr 组件面板中显示的样例组件
Lotus Quickr 组件面板中显示的样例组件
Lotus Quickr 组件面板中显示的样例组件

遵循以下步骤:

  1. 为组件条目定义字符串,这些字符串执行了本地化并被放到一组属性文件中。打开文件

    <quickr>\PortalServer\shared\app\nls\quickr_en.properties 其中包含两个额外字符串,一个用于组件标题,一个用于描述:

    unyte.meeting=Unyte Meeting
    new.unyte.meeting=New Unyte Meeting
  2. 保存并关闭文件。该步骤只提供了英语表示的字符串。如果支持其他语言的话,也必须编辑该属性文件的其他语言的版本。
  3. 为组件条目添加图像。这个 unyteMeeting.gif 图像被包含到本样例的源代码中。将此图像放到以下目录中:

    <quickr>\wp_profile\installedApps\<node>\wps.ear\wps.war\themes\html\QPG\images\quickr

  4. 设置一些样式。打开这个文件:

    <quickr>\wp_profile\installedApps\<node>\wps.ear\wps.war\themes\html\QPG\styles_theme.jspf

    导航到标签为 “customize drop down” 的部分,并在面板中复制日历组件使用的两种样式定义。随后修改 ID 和引用的图像文件,如清单 13 所示。

清单 13. 为组件面板条目添加样式
 div.actionSelect li.unyteMeeting{ 
	 background: ${colors.actionSelectBackground} 
	 url("images/quickr/unyteMeeting.gif") 
	 ${requestScope.cssRules.bidiLeft} 50% no-repeat; 
 } 

 #teamSpaceAddComponentForm h2.unyteMeeting{ 
	 background: ${colors.actionSelectBackground} 
	 url("images/quickr/unyteMeeting.gif") 
	 ${requestScope.cssRules.bidiLeft} 50% no-repeat; 
 }
  1. 保存并关闭文件。
  2. 接下来,需要将组件条目添加到面板中。打开以下文件:

    <quickr>\wp_profile\installedApps\<node>\wps.ear\wps.war\themes\html\QPG\pageHeaderContent.jsp

    搜索 ID 为 customizePage2 的 div。在这个 div 下,包含了面板的第二个页面中的组件条目集合。您将在其中一个现有条目的基础上开始处理。

  3. 复制 <li> 元素(它表示日历组件条目)并将元素的新副本直接粘贴到日历之后。现在修改类来匹配最后一步中生成的样式。
  4. 修改字符串以匹配您为组件生成的字符串。这些字符串从一个资源包中查找获得,因此它们是这个包中的关键字(需要指定)。
  5. 添加组件的窗口使用 portlet 的惟一 ID 将它添加到位置。这需要传递到 showTeamSpaceAddComponentForm( … ) 函数调用。现在您拥有了如清单 14 所示的代码。
清单 14. 将新条目添加到组件面板
 <li class="unyteMeeting"> 
 <a href="#" 
   onClick="javascript:showTeamSpaceAddComponentForm("
   <%=MarkupUtil.htmlAttributeEscape(MarkupUtil.jsEscape(pageHeaderText.getString 
   ("unyte.meeting")))%>", "<%=MarkupUtil.htmlAttributeEscape 
   (MarkupUtil.jsEscape(pageHeaderText.getString("new.unyte.meeting")))%> 
   ", "<%= applicationID %>", "wps.p.unyteMeeting", 
   true, nodesOnLevel, true, 'unyteMeeting')"
   class="picture"> 
	 <portal-fmt:text key="unyte.meeting" bundle="nls.quickr"/> 
 </a> 
 </li>

完成了这些修改后,您需要处理主题 JSP 文件,其中包含了经过编辑的 .jspf 片段,从而强制执行包含修改在内的重新编译操作。这个任务可以通过保存 JSP(无需编辑;只需改变修改时间戳)完成。需要以这种方式保存的文件是 styles.jsp and Default.jsp。

结束语

在本文中,我们展示了一种将 Sametime Unyte 服务集成到 Lotus Quickr 的简单方法。在 Lotus Quickr 集成 Lotus Sametime Unyte 功能允许用户从他们每天使用的应用程序中发起或预定 Web 会议。

使用 Web 会议软件的主要目标是允许人们在任何时间、任何地点与任何人交谈并共享信息。它帮助组织加快决策的制定,并通过减少差旅节省开支。

总的来说,将 Lotus Sametime Unyte Web 会议嵌入到 Lotus Quickr 可以帮助 IBM 用户完成以下任务:

  • 通过优质的通信通道解决问题以及提出并回答问题
  • 构建网络并与全世界范围内的同事建立联系
  • 较少甚至消除差旅需求,同时支持在组织内部实现即时的、全球范围内的协作
  • 集成先进的音频、视频和电话技术,帮助更顺利地展开会议

下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Lotus
ArticleID=430230
ArticleTitle=在 IBM Lotus Quickr 中构建定制组件集成 IBM Lotus Sametime Unyte Meeting
publish-date=09222009